From 610ec1a6a423bbf5ec0bf9d5c486570432ca4fb3 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 11 Aug 2022 23:53:52 +0000 Subject: [PATCH] Update database query syntax --- app/controllers/api/account.php | 24 ++-- app/controllers/api/databases.php | 135 ++++++++++-------- app/controllers/api/functions.php | 108 ++++++++------ app/controllers/api/projects.php | 110 +++++++------- app/controllers/api/storage.php | 82 ++++++----- app/controllers/api/teams.php | 70 +++++---- app/controllers/api/users.php | 34 +++-- app/controllers/general.php | 76 +++++----- app/controllers/shared/api.php | 20 +-- app/init.php | 49 ++++--- app/realtime.php | 2 +- app/tasks/maintenance.php | 4 +- app/tasks/migrate.php | 3 +- app/workers/certificates.php | 11 +- app/workers/deletes.php | 42 +++--- app/workers/functions.php | 7 +- composer.json | 20 ++- composer.lock | 72 ++++------ src/Appwrite/Migration/Migration.php | 7 +- src/Appwrite/Migration/Version/V12.php | 26 +++- src/Appwrite/Migration/Version/V14.php | 21 ++- src/Appwrite/Stats/UsageDB.php | 11 +- .../Utopia/Database/Validator/Queries.php | 9 +- .../e2e/Services/Databases/DatabasesBase.php | 30 ++-- .../Functions/FunctionsCustomClientTest.php | 2 +- 25 files changed, 552 insertions(+), 423 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 84b96c2bc0..79eb52cfc6 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -6,8 +6,11 @@ use Appwrite\Auth\Phone; use Appwrite\Auth\Validator\Password; use Appwrite\Auth\Validator\Phone as ValidatorPhone; use Appwrite\Detector\Detector; +use Appwrite\Event\Audit; use Appwrite\Event\Event; use Appwrite\Event\Mail; +use Appwrite\Event\Phone as EventPhone; +use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\Host; use Appwrite\Network\Validator\URL; @@ -15,13 +18,11 @@ use Appwrite\OpenSSL\OpenSSL; use Appwrite\Stats\Stats; use Appwrite\Template\Template; use Appwrite\URL\URL as URLParser; +use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; -use Appwrite\Utopia\Database\Validator\CustomId; use MaxMind\Db\Reader; use Utopia\App; -use Appwrite\Event\Audit; -use Appwrite\Event\Phone as EventPhone; use Utopia\Audit\Audit as EventAudit; use Utopia\Config\Config; use Utopia\Database\Database; @@ -32,7 +33,6 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Locale\Locale; -use Appwrite\Extend\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Range; @@ -165,7 +165,8 @@ App::post('/v1/account/sessions/email') $protocol = $request->getProtocol(); $profile = $dbForProject->findOne('users', [ - new Query('email', Query::TYPE_EQUAL, [$email])]); + Query::equal('email', [$email]), + ]); if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); // Wrong password or username @@ -448,8 +449,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } $user = ($user->isEmpty()) ? $dbForProject->findOne('sessions', [ // Get user by provider id - new Query('provider', QUERY::TYPE_EQUAL, [$provider]), - new Query('providerUid', QUERY::TYPE_EQUAL, [$oauth2ID]), + Query::equal('provider', [$provider]), + Query::equal('providerUid', [$oauth2ID]), ]) : $user; if ($user === false || $user->isEmpty()) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email @@ -462,7 +463,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $isVerified = $oauth2->isEmailVerified($accessToken); $user = $dbForProject->findOne('users', [ - new Query('email', Query::TYPE_EQUAL, [$email])]); + Query::equal('email', [$email]), + ]); if ($user === false || $user->isEmpty()) { // Last option -> create the user, generate random password $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -628,7 +630,7 @@ App::post('/v1/account/sessions/magic-url') $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); - $user = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); + $user = $dbForProject->findOne('users', [Query::equal('email', [$email])]); if (!$user) { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -869,7 +871,7 @@ App::post('/v1/account/sessions/phone') $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); - $user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$number])]); + $user = $dbForProject->findOne('users', [Query::equal('phone', [$number])]); if (!$user) { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -1954,7 +1956,7 @@ App::post('/v1/account/recovery') $email = \strtolower($email); $profile = $dbForProject->findOne('users', [ - new Query('email', Query::TYPE_EQUAL, [$email]) + Query::equal('email', [$email]), ]); if (!$profile) { diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 7b8dbf4037..6c942225f3 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -21,7 +21,7 @@ use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Key; use Utopia\Database\Validator\Permissions; -use Utopia\Database\Validator\QueryValidator; +use Utopia\Database\Validator\Query as QueryValidator; use Utopia\Database\Validator\Structure; use Utopia\Database\Validator\UID; use Utopia\Database\Exception\Authorization as AuthorizationException; @@ -248,31 +248,37 @@ App::get('/v1/databases') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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('usage') ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { + $filterQueries = []; + + if (!empty($search)) { + $filterQueries[] = 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); if ($cursorDocument->isEmpty()) { throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } - } - $queries = []; - - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } $usage->setParam('databases.read', 1); $response->dynamic(new Document([ - 'databases' => $dbForProject->find('databases', $queries, $limit, $offset, [], [$orderType], $cursorDocument ?? null, $cursorDirection), - 'total' => $dbForProject->count('databases', $queries, APP_LIMIT_COUNT), + 'databases' => $dbForProject->find('databases', \array_merge($filterQueries, $queries)), + 'total' => $dbForProject->count('databases', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_DATABASE_LIST); }); @@ -574,7 +580,7 @@ App::get('/v1/databases/:databaseId/collections') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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('usage') @@ -586,18 +592,24 @@ App::get('/v1/databases/:databaseId/collections') throw new Exception('Database not found', 404, Exception::DATABASE_NOT_FOUND); } - if (!empty($cursor)) { - $cursorCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor); + $filterQueries = []; - if ($cursorCollection->isEmpty()) { - throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } + if (!empty($search)) { + $filterQueries[] = 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); - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + if ($cursorDocument->isEmpty()) { + throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } $usage @@ -605,8 +617,8 @@ App::get('/v1/databases/:databaseId/collections') ->setParam('databases.collections.read', 1); $response->dynamic(new Document([ - 'collections' => $dbForProject->find('database_' . $database->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorCollection ?? null, $cursorDirection), - 'total' => $dbForProject->count('database_' . $database->getInternalId(), $queries, APP_LIMIT_COUNT), + 'collections' => $dbForProject->find('database_' . $database->getInternalId(), \array_merge($filterQueries, $queries)), + 'total' => $dbForProject->count('database_' . $database->getInternalId(), $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_COLLECTION_LIST); }); @@ -1295,7 +1307,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createDatetimeAttribute') ->label('sdk.description', '/docs/references/databases/create-datetime-attribute.md') - ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_DATETIME) ->param('databaseId', '', new UID(), 'Database ID.') @@ -1322,6 +1334,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti 'filters' => ['datetime'] ]), $response, $dbForProject, $database, $audits, $events, $usage); + $response->setStatusCode(Response::STATUS_CODE_ACCEPTED); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME); }); @@ -1575,8 +1588,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') } $count = $dbForProject->count('indexes', [ - new Query('collectionInternalId', Query::TYPE_EQUAL, [$collection->getInternalId()]), - new Query('databaseInternalId', Query::TYPE_EQUAL, [$db->getInternalId()]) + Query::equal('collectionInternalId', [$collection->getInternalId()]), + Query::equal('databaseInternalId', [$db->getInternalId()]) ], 61); $limit = 64 - MariaDB::getNumberOfDefaultIndexes(); @@ -1847,8 +1860,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->setParam('databaseId', $databaseId) ->setParam('collectionId', $collection->getId()) ->setParam('indexId', $index->getId()) - ->setContext('collection', $collection) - ->setContext('database', $db) + ->setContext('collection', $collection) + ->setContext('database', $db) ->setPayload($response->output($index, Response::MODEL_INDEX)) ; @@ -2003,7 +2016,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->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(['DESC', 'ASC'], true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of order directions for sorting attribtues. 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('orderTypes', [], new ArrayList(new WhiteList([Database::ORDER_DESC, Database::ORDER_ASC], true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of order directions for sorting attribtues. Possible values are DESC for descending order, or ASC for ascending order. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order types are allowed.', true) ->inject('response') ->inject('dbForProject') ->inject('usage') @@ -2036,31 +2049,17 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } } - $queries = \array_map(function ($query) { - $query = Query::parse($query); - - if (\count($query->getValues()) > 100) { - throw new Exception("You cannot use more than 100 query values on attribute '{$query->getAttribute()}'", 400, Exception::GENERAL_QUERY_LIMIT_EXCEEDED); - } - - return $query; + $filterQueries = \array_map(function ($query) { + return Query::parse($query); }, $queries); - if (!empty($orderAttributes)) { - $validator = new OrderAttributes($collection->getAttribute('attributes', []), $collection->getAttribute('indexes', []), true); - if (!$validator->isValid($orderAttributes)) { - throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID); - } + $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] ?? ''); } - if (!empty($queries)) { - $validator = new QueriesValidator(new QueryValidator($collection->getAttribute('attributes', [])), $collection->getAttribute('indexes', []), true); - if (!$validator->isValid($queries)) { - throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID); - } - } - - $cursorDocument = null; if (!empty($cursor)) { $cursorDocument = $collection->getAttribute('permission') === 'collection' ? Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor)) @@ -2069,15 +2068,27 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') if ($cursorDocument->isEmpty()) { throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } + + $otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + } + + $allQueries = \array_merge($filterQueries, $otherQueries); + + if (!empty($allQueries)) { + $attributes = $collection->getAttribute('attributes', []); + $validator = new QueriesValidator(new QueryValidator($attributes), $attributes, $collection->getAttribute('indexes', []), true); + if (!$validator->isValid($allQueries)) { + throw new Exception($validator->getDescription(), 400, Exception::GENERAL_QUERY_INVALID); + } } if ($collection->getAttribute('permission') === 'collection') { /** @var Document[] $documents */ - $documents = Authorization::skip(fn() => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection)); - $total = Authorization::skip(fn() => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT)); + $documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries)); + $total = Authorization::skip(fn () => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } else { - $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection); - $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); + $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries); + $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } /** @@ -2562,9 +2573,11 @@ App::get('/v1/databases/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { @@ -2674,9 +2687,11 @@ App::get('/v1/databases/:databaseId/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { @@ -2787,9 +2802,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index bb7280e316..71f2be493e 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -103,28 +103,34 @@ App::get('/v1/functions') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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) { - if (!empty($cursor)) { - $cursorFunction = $dbForProject->getDocument('functions', $cursor); + $filterQueries = []; - if ($cursorFunction->isEmpty()) { - throw new Exception("Function '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } + if (!empty($search)) { + $filterQueries[] = 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); - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + if ($cursorDocument->isEmpty()) { + throw new Exception("Function '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } $response->dynamic(new Document([ - 'functions' => $dbForProject->find('functions', $queries, $limit, $offset, [], [$orderType], $cursorFunction ?? null, $cursorDirection), - 'total' => $dbForProject->count('functions', $queries, APP_LIMIT_COUNT), + 'functions' => $dbForProject->find('functions', \array_merge($filterQueries, $queries)), + 'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_FUNCTION_LIST); }); @@ -236,9 +242,11 @@ App::get('/v1/functions/:functionId/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { @@ -553,9 +561,9 @@ App::post('/v1/functions/:functionId/deployments') if ($activate) { // Remove deploy for all other deployments. $activeDeployments = $dbForProject->find('deployments', [ - new Query('activate', Query::TYPE_EQUAL, [true]), - new Query('resourceId', Query::TYPE_EQUAL, [$functionId]), - new Query('resourceType', Query::TYPE_EQUAL, ['functions']) + Query::equal('activate', [true]), + Query::equal('resourceId', [$functionId]), + Query::equal('resourceType', ['functions']) ]); foreach ($activeDeployments as $activeDeployment) { @@ -643,7 +651,7 @@ App::get('/v1/functions/:functionId/deployments') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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) { @@ -654,25 +662,31 @@ App::get('/v1/functions/:functionId/deployments') throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } - if (!empty($cursor)) { - $cursorDeployment = $dbForProject->getDocument('deployments', $cursor); - - if ($cursorDeployment->isEmpty()) { - throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } - } - - $queries = []; + $filterQueries = []; if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + $filterQueries[] = Query::search('search', $search); } - $queries[] = new Query('resourceId', Query::TYPE_EQUAL, [$function->getId()]); - $queries[] = new Query('resourceType', Query::TYPE_EQUAL, ['functions']); + $filterQueries[] = Query::equal('resourceId', [$function->getId()]); + $filterQueries[] = Query::equal('resourceType', ['functions']); - $results = $dbForProject->find('deployments', $queries, $limit, $offset, [], [$orderType], $cursorDeployment ?? null, $cursorDirection); - $total = $dbForProject->count('deployments', $queries, APP_LIMIT_COUNT); + $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); + + if ($cursorDocument->isEmpty()) { + throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + } + + $results = $dbForProject->find('deployments', \array_merge($filterQueries, $queries)); + $total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT); foreach ($results as $result) { $build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', '')); @@ -996,24 +1010,30 @@ App::get('/v1/functions/:functionId/executions') throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } - if (!empty($cursor)) { - $cursorExecution = $dbForProject->getDocument('executions', $cursor); - - if ($cursorExecution->isEmpty()) { - throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } - } - - $queries = [ - new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]) + $filterQueries = [ + Query::equal('functionId', [$function->getId()]) ]; if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + $filterQueries[] = Query::search('search', $search); } - $results = $dbForProject->find('executions', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection); - $total = $dbForProject->count('executions', $queries, APP_LIMIT_COUNT); + $queries = []; + $queries[] = Query::limit($limit); + $queries[] = Query::offset($offset); + $queries[] = Query::orderDesc(''); + if (!empty($cursor)) { + $cursorDocument = $dbForProject->getDocument('executions', $cursor); + + if ($cursorDocument->isEmpty()) { + throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + } + + $results = $dbForProject->find('executions', \array_merge($filterQueries, $queries)); + $total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT); $response->dynamic(new Document([ 'executions' => $results, diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 151834d0ae..504d362b94 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -177,27 +177,33 @@ App::get('/v1/projects') ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Results offset. 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 project used as the starting point for the query, excluding the project itself. Should be used for efficient pagination when working with large sets of data. [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) + ->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('dbForConsole') ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForConsole) { - if (!empty($cursor)) { - $cursorProject = $dbForConsole->getDocument('projects', $cursor); + $filterQueries = []; - if ($cursorProject->isEmpty()) { - throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } + if (!empty($search)) { + $filterQueries[] = 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 = $dbForConsole->getDocument('projects', $cursor); - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + if ($cursorDocument->isEmpty()) { + throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } - $results = $dbForConsole->find('projects', $queries, $limit, $offset, [], [$orderType], $cursorProject ?? null, $cursorDirection); - $total = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT); + $results = $dbForConsole->find('projects', \array_merge($filterQueries, $queries)); + $total = $dbForConsole->count('projects', $filterQueries, APP_LIMIT_COUNT); $response->dynamic(new Document([ 'projects' => $results, @@ -294,9 +300,11 @@ App::get('/v1/projects/:projectId/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { @@ -634,8 +642,9 @@ App::get('/v1/projects/:projectId/webhooks') } $webhooks = $dbForConsole->find('webhooks', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) - ], 5000); + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(5000), + ]); $response->dynamic(new Document([ 'webhooks' => $webhooks, @@ -666,8 +675,8 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') } $webhook = $dbForConsole->findOne('webhooks', [ - new Query('_uid', Query::TYPE_EQUAL, [$webhookId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$webhookId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($webhook === false || $webhook->isEmpty()) { @@ -708,8 +717,8 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); $webhook = $dbForConsole->findOne('webhooks', [ - new Query('_uid', Query::TYPE_EQUAL, [$webhookId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$webhookId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($webhook === false || $webhook->isEmpty()) { @@ -754,8 +763,8 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') } $webhook = $dbForConsole->findOne('webhooks', [ - new Query('_uid', Query::TYPE_EQUAL, [$webhookId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$webhookId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($webhook === false || $webhook->isEmpty()) { @@ -792,8 +801,8 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') } $webhook = $dbForConsole->findOne('webhooks', [ - new Query('_uid', Query::TYPE_EQUAL, [$webhookId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$webhookId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($webhook === false || $webhook->isEmpty()) { @@ -875,8 +884,9 @@ App::get('/v1/projects/:projectId/keys') } $keys = $dbForConsole->find('keys', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]), - ], 5000); + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(5000), + ]); $response->dynamic(new Document([ 'keys' => $keys, @@ -907,8 +917,8 @@ App::get('/v1/projects/:projectId/keys/:keyId') } $key = $dbForConsole->findOne('keys', [ - new Query('_uid', Query::TYPE_EQUAL, [$keyId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$keyId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($key === false || $key->isEmpty()) { @@ -944,8 +954,8 @@ App::put('/v1/projects/:projectId/keys/:keyId') } $key = $dbForConsole->findOne('keys', [ - new Query('_uid', Query::TYPE_EQUAL, [$keyId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$keyId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($key === false || $key->isEmpty()) { @@ -987,8 +997,8 @@ App::delete('/v1/projects/:projectId/keys/:keyId') } $key = $dbForConsole->findOne('keys', [ - new Query('_uid', Query::TYPE_EQUAL, [$keyId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$keyId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($key === false || $key->isEmpty()) { @@ -1072,8 +1082,9 @@ App::get('/v1/projects/:projectId/platforms') } $platforms = $dbForConsole->find('platforms', [ - new Query('projectId', Query::TYPE_EQUAL, [$project->getId()]) - ], 5000); + Query::equal('projectId', [$project->getId()]), + Query::limit(5000), + ]); $response->dynamic(new Document([ 'platforms' => $platforms, @@ -1104,8 +1115,8 @@ App::get('/v1/projects/:projectId/platforms/:platformId') } $platform = $dbForConsole->findOne('platforms', [ - new Query('_uid', Query::TYPE_EQUAL, [$platformId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$platformId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($platform === false || $platform->isEmpty()) { @@ -1141,8 +1152,8 @@ App::put('/v1/projects/:projectId/platforms/:platformId') } $platform = $dbForConsole->findOne('platforms', [ - new Query('_uid', Query::TYPE_EQUAL, [$platformId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$platformId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($platform === false || $platform->isEmpty()) { @@ -1185,8 +1196,8 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') } $platform = $dbForConsole->findOne('platforms', [ - new Query('_uid', Query::TYPE_EQUAL, [$platformId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$platformId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($platform === false || $platform->isEmpty()) { @@ -1225,8 +1236,8 @@ App::post('/v1/projects/:projectId/domains') } $document = $dbForConsole->findOne('domains', [ - new Query('domain', Query::TYPE_EQUAL, [$domain]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]), + Query::equal('domain', [$domain]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($document && !$document->isEmpty()) { @@ -1285,8 +1296,9 @@ App::get('/v1/projects/:projectId/domains') } $domains = $dbForConsole->find('domains', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) - ], 5000); + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(5000), + ]); $response->dynamic(new Document([ 'domains' => $domains, @@ -1317,8 +1329,8 @@ App::get('/v1/projects/:projectId/domains/:domainId') } $domain = $dbForConsole->findOne('domains', [ - new Query('_uid', Query::TYPE_EQUAL, [$domainId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$domainId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($domain === false || $domain->isEmpty()) { @@ -1351,8 +1363,8 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') } $domain = $dbForConsole->findOne('domains', [ - new Query('_uid', Query::TYPE_EQUAL, [$domainId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$domainId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($domain === false || $domain->isEmpty()) { @@ -1411,8 +1423,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId') } $domain = $dbForConsole->findOne('domains', [ - new Query('_uid', Query::TYPE_EQUAL, [$domainId]), - new Query('projectInternalId', Query::TYPE_EQUAL, [$project->getInternalId()]) + Query::equal('_uid', [$domainId]), + Query::equal('projectInternalId', [$project->getInternalId()]), ]); if ($domain === false || $domain->isEmpty()) { diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 1da1202dd4..1c2c38b1db 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -158,27 +158,37 @@ App::get('/v1/storage/buckets') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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('usage') ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { - $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; + $filterQueries = []; + if (!empty($search)) { + $filterQueries[] = Query::search('name', $search); + } + + $queries = []; + $queries[] = Query::limit($limit); + $queries[] = Query::offset($offset); + $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); if (!empty($cursor)) { - $cursorBucket = $dbForProject->getDocument('buckets', $cursor); + $cursorDocument = $dbForProject->getDocument('buckets', $cursor); - if ($cursorBucket->isEmpty()) { + if ($cursorDocument->isEmpty()) { throw new Exception("Bucket '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } $usage->setParam('storage.buckets.read', 1); $response->dynamic(new Document([ - 'buckets' => $dbForProject->find('buckets', $queries, $limit, $offset, [], [$orderType], $cursorBucket ?? null, $cursorDirection), - 'total' => $dbForProject->count('buckets', $queries, APP_LIMIT_COUNT), + 'buckets' => $dbForProject->find('buckets', \array_merge($filterQueries, $queries)), + 'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_BUCKET_LIST); }); @@ -678,7 +688,7 @@ App::get('/v1/storage/buckets/:bucketId/files') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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('usage') @@ -702,34 +712,36 @@ App::get('/v1/storage/buckets/:bucketId/files') } } - $queries = [new Query('bucketId', Query::TYPE_EQUAL, [$bucketId])]; + $filterQueries = []; - if ($search) { - $queries[] = [new Query('name', Query::TYPE_SEARCH, [$search])]; - } - - if (!empty($cursor)) { - if ($bucket->getAttribute('permission') === 'bucket') { - $cursorFile = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor)); - } else { - $cursorFile = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor); - } - - if ($cursorFile->isEmpty()) { - throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } + if (!empty($search)) { + $filterQueries[] = Query::search('name', $search); } $queries = []; + $queries[] = Query::limit($limit); + $queries[] = Query::offset($offset); + $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); + if (!empty($cursor)) { + if ($bucket->getAttribute('permission') === 'bucket') { + $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor)); + } else { + $cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor); + } - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + if ($cursorDocument->isEmpty()) { + throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } if ($bucket->getAttribute('permission') === 'bucket') { - $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection)); + $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries))); + $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } else { - $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection); + $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries)); + $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } $usage @@ -739,7 +751,7 @@ App::get('/v1/storage/buckets/:bucketId/files') $response->dynamic(new Document([ 'files' => $files, - 'total' => $dbForProject->count('bucket_' . $bucket->getInternalId(), $queries, APP_LIMIT_COUNT), + 'total' => $total, ]), Response::MODEL_FILE_LIST); }); @@ -1526,9 +1538,11 @@ App::get('/v1/storage/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { @@ -1635,9 +1649,11 @@ App::get('/v1/storage/:bucketId/usage') $limit = $periods[$range]['limit']; $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 5238ea2f62..5b6503ccdc 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -127,22 +127,28 @@ App::get('/v1/teams') ->inject('dbForProject') ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { - if (!empty($cursor)) { - $cursorTeam = $dbForProject->getDocument('teams', $cursor); + $filterQueries = []; - if ($cursorTeam->isEmpty()) { - throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } + if (!empty($search)) { + $filterQueries[] = 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); - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + if ($cursorDocument->isEmpty()) { + throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } - $results = $dbForProject->find('teams', $queries, $limit, $offset, [], [$orderType], $cursorTeam ?? null, $cursorDirection); - $total = $dbForProject->count('teams', $queries, APP_LIMIT_COUNT); + $results = $dbForProject->find('teams', \array_merge($filterQueries, $queries)); + $total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT); $response->dynamic(new Document([ 'teams' => $results, @@ -237,8 +243,9 @@ App::delete('/v1/teams/:teamId') } $memberships = $dbForProject->find('memberships', [ - new Query('teamId', Query::TYPE_EQUAL, [$teamId]), - ], 2000, 0); // TODO fix members limit + Query::equal('teamId', [$teamId]), + Query::limit(2000), // TODO fix members limit + ]); // TODO delete all members individually from the user object foreach ($memberships as $membership) { @@ -313,7 +320,7 @@ App::post('/v1/teams/:teamId/memberships') throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } - $invitee = $dbForProject->findOne('users', [new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address + $invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address if (empty($invitee)) { // Create new user if no user with same email found $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -452,7 +459,7 @@ App::get('/v1/teams/:teamId/memberships') ->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', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', 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) { @@ -463,33 +470,34 @@ App::get('/v1/teams/:teamId/memberships') throw new Exception('Team not found', 404, Exception::TEAM_NOT_FOUND); } - if (!empty($cursor)) { - $cursorMembership = $dbForProject->getDocument('memberships', $cursor); - - if ($cursorMembership->isEmpty()) { - throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } - } - - $queries = [new Query('teamId', Query::TYPE_EQUAL, [$teamId])]; + $filterQueries = [Query::equal('teamId', [$teamId])]; if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + $filterQueries[] = 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); + + if ($cursorDocument->isEmpty()) { + throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } $memberships = $dbForProject->find( collection: 'memberships', - queries: $queries, - limit: $limit, - offset: $offset, - orderTypes: [$orderType], - cursor: $cursorMembership ?? null, - cursorDirection: $cursorDirection + queries: \array_merge($filterQueries, $otherQueries), ); $total = $dbForProject->count( - collection:'memberships', - queries: $queries, + collection: 'memberships', + queries: $filterQueries, max: APP_LIMIT_COUNT ); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a9458d7897..320ee44868 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -106,24 +106,30 @@ App::get('/v1/users') ->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 user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data. [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) + ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForProject') ->inject('usage') ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, Stats $usage) { - if (!empty($cursor)) { - $cursorUser = $dbForProject->getDocument('users', $cursor); + $filterQueries = []; - if ($cursorUser->isEmpty()) { - throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); - } + if (!empty($search)) { + $filterQueries[] = 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('users', $cursor); - if (!empty($search)) { - $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); + if ($cursorDocument->isEmpty()) { + throw new Exception("User '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND); + } + + $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); } $usage @@ -131,8 +137,8 @@ App::get('/v1/users') ; $response->dynamic(new Document([ - 'users' => $dbForProject->find('users', $queries, $limit, $offset, [], [$orderType], $cursorUser ?? null, $cursorDirection), - 'total' => $dbForProject->count('users', $queries, APP_LIMIT_COUNT), + 'users' => $dbForProject->find('users', \array_merge($filterQueries, $queries)), + 'total' => $dbForProject->count('users', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_USER_LIST); }); @@ -905,9 +911,11 @@ App::get('/v1/users/usage') $period = $periods[$range]['period']; $requestDocs = $dbForProject->find('stats', [ - new Query('period', Query::TYPE_EQUAL, [$period]), - new Query('metric', Query::TYPE_EQUAL, [$metric]), - ], $limit, 0, ['time'], [Database::ORDER_DESC]); + Query::equal('period', [$period]), + Query::equal('metric', [$metric]), + Query::limit($limit), + Query::orderDesc('time'), + ]); $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { diff --git a/app/controllers/general.php b/app/controllers/general.php index 05a6b69e7e..068fac3ccb 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -22,8 +22,8 @@ use Appwrite\Utopia\Response\Filters\V13 as ResponseV13; use Appwrite\Utopia\Response\Filters\V14 as ResponseV14; use Utopia\CLI\Console; use Utopia\Database\Database; -use Utopia\Database\Document; use Utopia\Database\DateTime; +use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Hostname; @@ -90,7 +90,7 @@ App::init() if (!empty($envDomain) && $envDomain !== 'localhost') { $mainDomain = $envDomain; } else { - $domainDocument = $dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']); + $domainDocument = $dbForConsole->findOne('domains', [Query::orderAsc('_id')]); $mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get(); } @@ -98,7 +98,7 @@ App::init() Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.'); } else { $domainDocument = $dbForConsole->findOne('domains', [ - new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()]) + Query::equal('domain', [$domain->get()]) ]); if (!$domainDocument) { @@ -173,42 +173,42 @@ App::init() ? null : '.' . $request->getHostname()); - /* - * Response format - */ - $responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', '')); - if ($responseFormat) { - switch ($responseFormat) { - case version_compare($responseFormat, '0.11.2', '<='): - Response::setFilter(new ResponseV11()); - break; - case version_compare($responseFormat, '0.12.4', '<='): - Response::setFilter(new ResponseV12()); - break; - case version_compare($responseFormat, '0.13.4', '<='): - Response::setFilter(new ResponseV13()); - break; - case version_compare($responseFormat, '0.14.0', '<='): - Response::setFilter(new ResponseV14()); - break; - default: - Response::setFilter(null); - } - } else { - Response::setFilter(null); - } - - /* - * Security Headers - * - * As recommended at: - * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers - */ - if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS - if ($request->getProtocol() !== 'https') { - if ($request->getMethod() !== Request::METHOD_GET) { - throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED); + /* + * Response format + */ + $responseFormat = $request->getHeader('x-appwrite-response-format', App::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', '')); + if ($responseFormat) { + switch ($responseFormat) { + case version_compare($responseFormat, '0.11.2', '<='): + Response::setFilter(new ResponseV11()); + break; + case version_compare($responseFormat, '0.12.4', '<='): + Response::setFilter(new ResponseV12()); + break; + case version_compare($responseFormat, '0.13.4', '<='): + Response::setFilter(new ResponseV13()); + break; + case version_compare($responseFormat, '0.14.0', '<='): + Response::setFilter(new ResponseV14()); + break; + default: + Response::setFilter(null); } + } else { + Response::setFilter(null); + } + + /* + * Security Headers + * + * As recommended at: + * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers + */ + if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS + if ($request->getProtocol() !== 'https') { + if ($request->getMethod() !== Request::METHOD_GET) { + throw new AppwriteException('Method unsupported over HTTP.', 500, AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED); + } return $response->redirect('https://' . $request->getHostname() . $request->getURI()); } diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 6ba9bfab5a..1ba37eb920 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -45,20 +45,20 @@ App::init() /* * Abuse Check */ - $abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}'); - $timeLimitArray = []; + $abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}'); + $timeLimitArray = []; $abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel; - foreach ($abuseKeyLabel as $abuseKey) { - $timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject); - $timeLimit + foreach ($abuseKeyLabel as $abuseKey) { + $timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject); + $timeLimit ->setParam('{userId}', $user->getId()) ->setParam('{userAgent}', $request->getUserAgent('')) ->setParam('{ip}', $request->getIP()) ->setParam('{url}', $request->getHostname() . $route->getPath()); - $timeLimitArray[] = $timeLimit; - } + $timeLimitArray[] = $timeLimit; + } $closestLimit = null; @@ -99,11 +99,11 @@ App::init() /* * Background Jobs */ - $events + $events ->setEvent($route->getLabel('event', '')) ->setProject($project) ->setUser($user) - ; + ; $mails ->setProject($project) @@ -184,7 +184,7 @@ App::init() default: throw new Exception('Unsupported authentication route', 501, Exception::USER_AUTH_METHOD_UNSUPPORTED); - } + } }); App::shutdown() diff --git a/app/init.php b/app/init.php index 9c2bf32fed..03709727ae 100644 --- a/app/init.php +++ b/app/init.php @@ -268,9 +268,10 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return $database ->find('attributes', [ - new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]), - new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')]) - ], $database->getAttributeLimit(), 0, []); + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getAttributeLimit()), + ]); } ); @@ -282,9 +283,10 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return $database ->find('indexes', [ - new Query('collectionInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]), - new Query('databaseInternalId', Query::TYPE_EQUAL, [$document->getAttribute('databaseInternalId')]) - ], 64); + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit(64), + ]); } ); @@ -296,8 +298,9 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return $database ->find('platforms', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY); + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); } ); @@ -309,8 +312,9 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return $database ->find('domains', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY); + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); } ); @@ -322,8 +326,9 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return $database ->find('keys', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY); + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); } ); @@ -335,8 +340,9 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return $database ->find('webhooks', [ - new Query('projectInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY); + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); } ); @@ -347,8 +353,9 @@ Database::addFilter( }, function (mixed $value, Document $document, Database $database) { return Authorization::skip(fn () => $database->find('sessions', [ - new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY)); + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); } ); @@ -360,8 +367,9 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return Authorization::skip(fn() => $database ->find('tokens', [ - new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY)); + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); } ); @@ -373,8 +381,9 @@ Database::addFilter( function (mixed $value, Document $document, Database $database) { return Authorization::skip(fn() => $database ->find('memberships', [ - new Query('userInternalId', Query::TYPE_EQUAL, [$document->getInternalId()]) - ], APP_LIMIT_SUBQUERY)); + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); } ); diff --git a/app/realtime.php b/app/realtime.php index b6f96a86fc..d023b49e45 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -209,7 +209,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $payload = []; $list = Authorization::skip(fn () => $database->find('realtime', [ - new Query('timestamp', Query::TYPE_GREATER, [DateTime::addSeconds(new \DateTime(), -15)]) + Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)), ])); /** diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php index af4943bd8c..e5d6eb3755 100644 --- a/app/tasks/maintenance.php +++ b/app/tasks/maintenance.php @@ -108,8 +108,8 @@ $cli $time = DateTime::now(); $certificates = $dbForConsole->find('certificates', [ - new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts - new Query('renewDate', Query::TYPE_LESSEREQUAL, [$time]) // includes 60 days cooldown (we have 30 days to renew) + Query::lessThanEqual('attempts', 5), // Maximum 5 attempts + Query::lessThanEqual('renewDate', $time) // includes 60 days cooldown (we have 30 days to renew) ], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains) diff --git a/app/tasks/migrate.php b/app/tasks/migrate.php index ff5ec3593f..c950cf1bf6 100644 --- a/app/tasks/migrate.php +++ b/app/tasks/migrate.php @@ -9,6 +9,7 @@ use Utopia\Cache\Cache; use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Database; +use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Text; @@ -70,7 +71,7 @@ $cli } $sum = \count($projects); - $projects = $consoleDB->find('projects', limit: $limit, offset: $offset); + $projects = $consoleDB->find('projects', [Query::limit($limit), Query::offset($offset)]); $offset = $offset + $limit; $count = $count + $sum; diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 9c8dc3c34f..42ead45a26 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -73,7 +73,7 @@ class CertificatesV1 extends Worker $domain = new Domain($document->getAttribute('domain', '')); // Get current certificate - $certificate = $this->dbForConsole->findOne('certificates', [new Query('domain', Query::TYPE_EQUAL, [$domain->get()])]); + $certificate = $this->dbForConsole->findOne('certificates', [Query::equal('domain', [$domain->get()])]); // If we don't have certificate for domain yet, let's create new document. At the end we save it if (!$certificate) { @@ -151,7 +151,7 @@ class CertificatesV1 extends Worker private function saveCertificateDocument(string $domain, Document $certificate): void { // Check if update or insert required - $certificateDocument = $this->dbForConsole->findOne('certificates', [new Query('domain', Query::TYPE_EQUAL, [$domain])]); + $certificateDocument = $this->dbForConsole->findOne('certificates', [Query::equal('domain', [$domain])]); if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) { // Merge new data with current data $certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy())); @@ -176,7 +176,7 @@ class CertificatesV1 extends Worker if (!empty($envDomain) && $envDomain !== 'localhost') { return $envDomain; } else { - $domainDocument = $this->dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']); + $domainDocument = $this->dbForConsole->findOne('domains', [Query::orderAsc('_id')]); if ($domainDocument) { return $domainDocument->getAttribute('domain'); } @@ -394,8 +394,9 @@ class CertificatesV1 extends Worker private function updateDomainDocuments(string $certificateId, string $domain): void { $domains = $this->dbForConsole->find('domains', [ - new Query('domain', Query::TYPE_EQUAL, [$domain]) - ], 1000); + Query::equal('domain', [$domain]), + Query::limit(1000), + ]); foreach ($domains as $domainDocument) { $domainDocument->setAttribute('updated', DateTime::now()); diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 3605f2c93f..495be727df 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -149,11 +149,11 @@ class DeletesV1 extends Worker $dbForProject->deleteCollection('database_' . $databaseId . '_collection_' . $document->getInternalId()); $this->deleteByGroup('attributes', [ - new Query('collectionId', Query::TYPE_EQUAL, [$collectionId]) + Query::equal('collectionId', [$collectionId]) ], $dbForProject); $this->deleteByGroup('indexes', [ - new Query('collectionId', Query::TYPE_EQUAL, [$collectionId]) + Query::equal('collectionId', [$collectionId]) ], $dbForProject); $this->deleteAuditLogsByResource('collection/' . $collectionId, $projectId); @@ -169,13 +169,13 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); // Delete Usage stats $this->deleteByGroup('stats', [ - new Query('time', Query::TYPE_LESSER, [$datetime1d]), - new Query('period', Query::TYPE_EQUAL, ['1d']), + Query::lessThan('time', $datetime1d), + Query::equal('period', ['1d']), ], $dbForProject); $this->deleteByGroup('stats', [ - new Query('time', Query::TYPE_LESSER, [$datetime30m]), - new Query('period', Query::TYPE_EQUAL, ['30m']), + Query::lessThan('time', [$datetime30m]), + Query::equal('period', ['30m']), ], $dbForProject); }); } @@ -190,7 +190,7 @@ class DeletesV1 extends Worker // Delete Memberships $this->deleteByGroup('memberships', [ - new Query('teamId', Query::TYPE_EQUAL, [$teamId]) + Query::equal('teamId', [$teamId]) ], $this->getProjectDB($projectId)); } @@ -222,14 +222,14 @@ class DeletesV1 extends Worker // Delete all sessions of this user from the sessions table and update the sessions field of the user record $this->deleteByGroup('sessions', [ - new Query('userId', Query::TYPE_EQUAL, [$userId]) + Query::equal('userId', [$userId]) ], $this->getProjectDB($projectId)); $this->getProjectDB($projectId)->deleteCachedDocument('users', $userId); // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ - new Query('userId', Query::TYPE_EQUAL, [$userId]) + Query::equal('userId', [$userId]) ], $this->getProjectDB($projectId), function (Document $document) use ($projectId) { if ($document->getAttribute('confirm')) { // Count only confirmed members @@ -250,7 +250,7 @@ class DeletesV1 extends Worker // Delete tokens $this->deleteByGroup('tokens', [ - new Query('userId', Query::TYPE_EQUAL, [$userId]) + Query::equal('userId', [$userId]) ], $this->getProjectDB($projectId)); } @@ -263,7 +263,7 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); // Delete Executions $this->deleteByGroup('executions', [ - new Query('$createdAt', Query::TYPE_LESSER, [$datetime]) + Query::lessThan('$createdAt', $datetime) ], $dbForProject); }); } @@ -277,7 +277,7 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); // Delete Sessions $this->deleteByGroup('sessions', [ - new Query('expire', Query::TYPE_LESSER, [$datetime]) + Query::lessThan('expire', $datetime) ], $dbForProject); }); } @@ -291,7 +291,7 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); // Delete Dead Realtime Logs $this->deleteByGroup('realtime', [ - new Query('timestamp', Query::TYPE_LESSER, [$datetime]) + Query::lessThan('timestamp', $datetime) ], $dbForProject); }); } @@ -346,7 +346,7 @@ class DeletesV1 extends Worker $dbForProject = $this->getProjectDB($projectId); $this->deleteByGroup(Audit::COLLECTION, [ - new Query('resource', Query::TYPE_EQUAL, [$resource]) + Query::equal('resource', [$resource]) ], $dbForProject); } @@ -366,7 +366,7 @@ class DeletesV1 extends Worker $storageFunctions = new Local(APP_STORAGE_FUNCTIONS . '/app-' . $projectId); $deploymentIds = []; $this->deleteByGroup('deployments', [ - new Query('resourceId', Query::TYPE_EQUAL, [$functionId]) + Query::equal('resourceId', [$functionId]) ], $dbForProject, function (Document $document) use ($storageFunctions, &$deploymentIds) { $deploymentIds[] = $document->getId(); if ($storageFunctions->delete($document->getAttribute('path', ''), true)) { @@ -383,7 +383,7 @@ class DeletesV1 extends Worker $storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId); foreach ($deploymentIds as $deploymentId) { $this->deleteByGroup('builds', [ - new Query('deploymentId', Query::TYPE_EQUAL, [$deploymentId]) + Query::equal('deploymentId', [$deploymentId]) ], $dbForProject, function (Document $document) use ($storageBuilds, $deploymentId) { if ($storageBuilds->delete($document->getAttribute('outputPath', ''), true)) { Console::success('Deleted build files: ' . $document->getAttribute('outputPath', '')); @@ -398,7 +398,7 @@ class DeletesV1 extends Worker */ Console::info("Deleting executions for function " . $functionId); $this->deleteByGroup('executions', [ - new Query('functionId', Query::TYPE_EQUAL, [$functionId]) + Query::equal('functionId', [$functionId]) ], $dbForProject); /** @@ -442,7 +442,7 @@ class DeletesV1 extends Worker Console::info("Deleting builds for deployment " . $deploymentId); $storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId); $this->deleteByGroup('builds', [ - new Query('deploymentId', Query::TYPE_EQUAL, [$deploymentId]) + Query::equal('deploymentId', [$deploymentId]) ], $dbForProject, function (Document $document) use ($storageBuilds) { if ($storageBuilds->delete($document->getAttribute('outputPath', ''), true)) { Console::success('Deleted build files: ' . $document->getAttribute('outputPath', '')); @@ -501,7 +501,7 @@ class DeletesV1 extends Worker $executionStart = \microtime(true); while ($sum === $limit) { - $projects = $this->getConsoleDB()->find('projects', [], $limit, ($chunk * $limit)); + $projects = $this->getConsoleDB()->find('projects', [Query::limit($limit), Query::offset($chunk * $limit)]); $chunk++; @@ -540,7 +540,7 @@ class DeletesV1 extends Worker while ($sum === $limit) { $chunk++; - $results = $database->find($collection, $queries, $limit, 0); + $results = $database->find($collection, \array_merge([Query::limit($limit)], $queries)); $sum = count($results); @@ -567,7 +567,7 @@ class DeletesV1 extends Worker // If domain has certificate generated if (isset($document['certificateId'])) { $domainUsingCertificate = $consoleDB->findOne('domains', [ - new Query('certificateId', Query::TYPE_EQUAL, [$document['certificateId']]) + Query::equal('certificateId', [$document['certificateId']]) ]); if (!$domainUsingCertificate) { diff --git a/app/workers/functions.php b/app/workers/functions.php index 3560518135..4fa04e5bb9 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -14,6 +14,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Query; require_once __DIR__ . '/../init.php'; @@ -61,7 +62,11 @@ class FunctionsV1 extends Worker /** @var Document[] $functions */ while ($sum >= $limit) { - $functions = $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC]); + $functions = $database->find('functions', [ + Query::limit($limit), + Query::offset($offset), + Query::orderAsc('name'), + ]); $sum = \count($functions); $offset = $offset + $limit; diff --git a/composer.json b/composer.json index 5fa507dcca..a359ed4dfa 100644 --- a/composer.json +++ b/composer.json @@ -44,13 +44,13 @@ "appwrite/php-runtimes": "0.10.*", "utopia-php/framework": "0.20.*", "utopia-php/logger": "0.3.*", - "utopia-php/abuse": "dev-origin/timestamp-to-datetime as 0.7.2", + "utopia-php/abuse": "dev-feat-update-database-queries", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "dev-origin/unix-to-datetime as 0.8.2", + "utopia-php/audit": "dev-feat-update-database-queries", "utopia-php/cache": "0.6.*", "utopia-php/cli": "0.13.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.19.*", + "utopia-php/database": "dev-feat-query-gen-2.3", "utopia-php/locale": "0.4.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", @@ -73,6 +73,18 @@ { "url": "https://github.com/appwrite/runtimes.git", "type": "git" + }, + { + "url": "https://github.com/utopia-php/database.git", + "type": "git" + }, + { + "url": "https://github.com/utopia-php/abuse.git", + "type": "git" + }, + { + "url": "https://github.com/utopia-php/audit.git", + "type": "git" } ], "require-dev": { @@ -90,4 +102,4 @@ "php": "8.0" } } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index 903b7476a4..1fb7ef5edd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "993486075710ab0cdbba6c33f0b09218", + "content-hash": "31918539abb7d1e82ca84ed8fc21969e", "packages": [ { "name": "adhocore/jwt", @@ -1733,22 +1733,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.7.0", + "version": "dev-feat-update-database-queries", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "52fb20e39e2e9619948bc0a73b52e10caa71350d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/52fb20e39e2e9619948bc0a73b52e10caa71350d", - "reference": "52fb20e39e2e9619948bc0a73b52e10caa71350d", - "shasum": "" + "reference": "5c200defd69155c956fc127e8e1771ef820f9dad" }, "require": { "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": ">=0.11 <1.0" + "utopia-php/database": "dev-feat-query-gen-2.3" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1760,7 +1754,6 @@ "Utopia\\Abuse\\": "src/Abuse" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1772,17 +1765,13 @@ ], "description": "A simple abuse library to manage application usage limits", "keywords": [ - "Abuse", + "abuse", "framework", "php", "upf", "utopia" ], - "support": { - "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.7.0" - }, - "time": "2021-12-27T13:06:45+00:00" + "time": "2022-08-10T19:51:33+00:00" }, { "name": "utopia-php/analytics", @@ -1841,22 +1830,16 @@ }, { "name": "utopia-php/audit", - "version": "0.8.0", + "version": "dev-feat-update-database-queries", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "b46dc42614a69437c45eb229249b6a6d000122c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/b46dc42614a69437c45eb229249b6a6d000122c1", - "reference": "b46dc42614a69437c45eb229249b6a6d000122c1", - "shasum": "" + "reference": "165d4f74fedf31c98c038eb92a0516472885a78a" }, "require": { "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": ">=0.11 <1.0" + "utopia-php/database": "dev-feat-query-gen-2.3" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1868,7 +1851,6 @@ "Utopia\\Audit\\": "src/Audit" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -1880,17 +1862,13 @@ ], "description": "A simple audit library to manage application users logs", "keywords": [ - "Audit", + "audit", "framework", "php", "upf", "utopia" ], - "support": { - "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.8.0" - }, - "time": "2021-12-27T13:05:56+00:00" + "time": "2022-08-10T20:15:22+00:00" }, { "name": "utopia-php/cache", @@ -2051,17 +2029,11 @@ }, { "name": "utopia-php/database", - "version": "0.18.9", + "version": "dev-feat-query-gen-2.3", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "227b3ca919149b7b0d6556c8effe9ee46ed081e6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/227b3ca919149b7b0d6556c8effe9ee46ed081e6", - "reference": "227b3ca919149b7b0d6556c8effe9ee46ed081e6", - "shasum": "" + "reference": "d6ba28d373e2cad2fc5d916564fd245e17bea38f" }, "require": { "ext-mongodb": "*", @@ -2085,7 +2057,11 @@ "Utopia\\Database\\": "src/Database" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/Database" + } + }, "license": [ "MIT" ], @@ -2107,11 +2083,7 @@ "upf", "utopia" ], - "support": { - "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.18.9" - }, - "time": "2022-07-19T09:42:53+00:00" + "time": "2022-08-11T19:26:25+00:00" }, { "name": "utopia-php/domains", @@ -5348,7 +5320,11 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/abuse": 20, + "utopia-php/audit": 20, + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index a92e232719..7be2ccefbe 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -5,6 +5,7 @@ namespace Appwrite\Migration; use Swoole\Runtime; use Utopia\Database\Document; use Utopia\Database\Database; +use Utopia\Database\Query; use Utopia\CLI\Console; use Utopia\Config\Config; use Exception; @@ -116,7 +117,11 @@ abstract class Migration Console::log('Migrating Collection ' . $collection['$id'] . ':'); do { - $documents = $this->projectDB->find($collection['$id'], limit: $this->limit, cursor: $nextDocument); + $queries = [Query::limit($this->limit)]; + if ($nextDocument !== null) { + $queries[] = Query::cursorAfter($nextDocument); + } + $documents = $this->projectDB->find($collection['$id'], $queries); $count = count($documents); $sum += $count; diff --git a/src/Appwrite/Migration/Version/V12.php b/src/Appwrite/Migration/Version/V12.php index 78d6407519..c7a193f4ef 100644 --- a/src/Appwrite/Migration/Version/V12.php +++ b/src/Appwrite/Migration/Version/V12.php @@ -156,7 +156,7 @@ class V12 extends Migration */ $this->createCollection('buckets'); - if (!$this->projectDB->findOne('buckets', [new Query('$id', Query::TYPE_EQUAL, ['default'])])) { + if (!$this->projectDB->findOne('buckets', [Query::equal('$id', ['default'])])) { $this->projectDB->createDocument('buckets', new Document([ '$id' => 'default', '$collection' => 'buckets', @@ -180,7 +180,11 @@ class V12 extends Migration */ $nextDocument = null; do { - $documents = $this->projectDB->find('files', limit: $this->limit, cursor: $nextDocument); + $queries = [Query::limit($this->limit)]; + if ($nextDocument !== null) { + $queries[] = Query::cursorAfter($nextDocument); + } + $documents = $this->projectDB->find('files', $queries); $count = count($documents); \Co\run(function (array $documents) { foreach ($documents as $document) { @@ -344,7 +348,11 @@ class V12 extends Migration $nextCollection = null; do { - $documents = $this->projectDB->find('collections', limit: $this->limit, cursor: $nextCollection); + $queries = [Query::limit($this->limit)]; + if ($nextCollection !== null) { + $queries[] = Query::cursorAfter($nextCollection); + } + $documents = $this->projectDB->find('collections', $queries); $count = count($documents); \Co\run(function (array $documents) { @@ -387,7 +395,11 @@ class V12 extends Migration $nextDocument = null; do { - $documents = $this->projectDB->find('collection_' . $internalId, limit: $this->limit, cursor: $nextDocument); + $queries = [Query::limit($this->limit)]; + if ($nextDocument !== null) { + $queries[] = Query::cursorAfter($nextDocument); + } + $documents = $this->projectDB->find('collection_' . $internalId, $queries); $count = count($documents); foreach ($documents as $document) { @@ -462,7 +474,11 @@ class V12 extends Migration $nextDocument = null; do { - $documents = $this->projectDB->find($id, limit: $this->limit, cursor: $nextDocument); + $queries = [Query::limit($this->limit)]; + if ($nextDocument !== null) { + $queries[] = Query::cursorAfter($nextDocument); + } + $documents = $this->projectDB->find($id, $queries); $count = count($documents); \Co\run(function (array $documents) { diff --git a/src/Appwrite/Migration/Version/V14.php b/src/Appwrite/Migration/Version/V14.php index 7d7d551ec5..27ef4848eb 100644 --- a/src/Appwrite/Migration/Version/V14.php +++ b/src/Appwrite/Migration/Version/V14.php @@ -87,7 +87,11 @@ class V14 extends Migration { $nextFile = null; do { - $documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", limit: $this->limit, cursor: $nextFile); + $queries = [Query::limit($this->limit)]; + if ($nextFile !== null) { + $queries[] = Query::cursorAfter($nextFile); + } + $documents = $this->projectDB->find("bucket_{$bucket->getInternalId()}", $queries); $count = count($documents); foreach ($documents as $document) { @@ -164,7 +168,11 @@ class V14 extends Migration $nextCollection = null; do { - $documents = $this->projectDB->find('database_1', limit: $this->limit, cursor: $nextCollection); + $queries = [Query::limit($this->limit)]; + if ($nextCollection !== null) { + $queries[] = Query::cursorAfter($nextCollection); + } + $documents = $this->projectDB->find('database_1', $queries); $count = count($documents); \Co\run(function (array $documents) { @@ -235,10 +243,15 @@ class V14 extends Migration * Offset pagination instead of cursor, since documents are re-created! */ $offset = 0; - $attributesCount = $this->projectDB->count($type, queries: [new Query('collectionId', Query::TYPE_EQUAL, [$collection->getId()])]); + $attributesCount = $this->projectDB->count($type, queries: [Query::equal('collectionId', [$collection->getId()])]); do { - $documents = $this->projectDB->find($type, limit: $this->limit, offset: $offset, queries: [new Query('collectionId', Query::TYPE_EQUAL, [$collection->getId()])]); + $queries = [ + Query::limit($this->limit), + Query::offset($offset), + Query::equal('collectionId', [$collection->getId()]), + ]; + $documents = $this->projectDB->find($type, $queries); $offset += $this->limit; foreach ($documents as $document) { diff --git a/src/Appwrite/Stats/UsageDB.php b/src/Appwrite/Stats/UsageDB.php index e159295792..fef6b38e7c 100644 --- a/src/Appwrite/Stats/UsageDB.php +++ b/src/Appwrite/Stats/UsageDB.php @@ -5,6 +5,7 @@ namespace Appwrite\Stats; use Exception; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; class UsageDB extends Usage { @@ -60,7 +61,7 @@ class UsageDB extends Usage $document->setAttribute('value', $value) ); } - } catch (Exception$e) { // if projects are deleted this might fail + } catch (\Exception $e) { // if projects are deleted this might fail if (is_callable($this->errorHandler)) { call_user_func($this->errorHandler, $e, "sync_project_{$projectId}_metric_{$metric}"); } else { @@ -91,8 +92,12 @@ class UsageDB extends Usage while ($sum === $limit) { try { - $results = $this->database->find($collection, $queries, $limit, cursor:$latestDocument); - } catch (Exception $e) { + $paginationQueries = [Query::limit($limit)]; + if ($latestDocument !== null) { + $paginationQueries[] = Query::cursorAfter($latestDocument); + } + $results = $this->database->find($collection, \array_merge($paginationQueries, $queries)); + } catch (\Exception $e) { if (is_callable($this->errorHandler)) { call_user_func($this->errorHandler, $e, "fetch_documents_project_{$projectId}_collection_{$collection}"); return; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries.php b/src/Appwrite/Utopia/Database/Validator/Queries.php index 02ffab1838..6be381dbf7 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries.php @@ -9,18 +9,21 @@ class Queries extends ValidatorQueries { /** * Expression constructor + * + * This Queries Validator that filters indexes for only available indexes * + * @param QueryValidator $validator * @param Document[] $attributes * @param Document[] $indexes * @param bool $strict */ - public function __construct($attributes, $indexes, $strict) + public function __construct($validator, $attributes = [], $indexes = [], $strict = true) { // Remove failed/stuck/processing indexes - $indexes = \array_filter($indexes, function ($index) { + $availableIndexes = \array_filter($indexes, function ($index) { return $index->getAttribute('status') === 'available'; }); - parent::__construct($attributes, $indexes, $strict); + parent::__construct($validator, $attributes, $availableIndexes, $strict); } } diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 398071c22e..ac671d2715 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -197,7 +197,7 @@ trait DatabasesBase $this->assertEquals($actors['body']['required'], false); $this->assertEquals($actors['body']['array'], true); - $this->assertEquals($datetime['headers']['status-code'], 201); + $this->assertEquals($datetime['headers']['status-code'], 202); $this->assertEquals($datetime['body']['key'], 'birthDay'); $this->assertEquals($datetime['body']['type'], 'datetime'); $this->assertEquals($datetime['body']['required'], false); @@ -410,7 +410,7 @@ trait DatabasesBase $this->assertEquals(false, $boolean['body']['array']); $this->assertEquals(true, $boolean['body']['default']); - $this->assertEquals(201, $datetime['headers']['status-code']); + $this->assertEquals(202, $datetime['headers']['status-code']); $this->assertEquals('datetime', $datetime['body']['key']); $this->assertEquals('datetime', $datetime['body']['type']); $this->assertEquals(false, $datetime['body']['required']); @@ -834,7 +834,7 @@ trait DatabasesBase 'attributes' => ['birthDay'], ]); - $this->assertEquals(201, $releaseWithDate['headers']['status-code']); + $this->assertEquals(202, $releaseWithDate['headers']['status-code']); $this->assertEquals('birthDay', $releaseWithDate['body']['key']); $this->assertEquals('key', $releaseWithDate['body']['type']); $this->assertCount(1, $releaseWithDate['body']['attributes']); @@ -1359,7 +1359,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['title.search("Captain America")'], + 'queries' => ['search("title", "Captain America")'], ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -1370,7 +1370,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['$id.equal("' . $documents['body']['documents'][0]['$id'] . '")'], + 'queries' => ['equal("$id", "' . $documents['body']['documents'][0]['$id'] . '")'], ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -1381,7 +1381,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['title.search("Homecoming")'], + 'queries' => ['search("title", "Homecoming")'], ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -1392,7 +1392,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['title.search("spider")'], + 'queries' => ['search("title", "spider")'], ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -1404,7 +1404,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['releaseYear.equal(1944)'], + 'queries' => ['equal("releaseYear", 1944)'], ]); $this->assertCount(1, $documents['body']['documents']); @@ -1414,7 +1414,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['releaseYear.notEqual(1944)'], + 'queries' => ['notEqual("releaseYear", 1944)'], ]); $this->assertCount(2, $documents['body']['documents']); @@ -1425,7 +1425,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['$createdAt.greater("1976-06-12")'], + 'queries' => ['greaterThan("$createdAt", "1976-06-12")'], ]); $this->assertCount(3, $documents['body']['documents']); @@ -1434,7 +1434,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['$createdAt.lesser("1976-06-12")'], + 'queries' => ['lessThan("$createdAt", "1976-06-12")'], ]); $this->assertCount(0, $documents['body']['documents']); @@ -1446,7 +1446,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['actors.equal("Tom Holland")'], + 'queries' => ['equal("actors", "Tom Holland")'], ]); $this->assertEquals(400, $documents['headers']['status-code']); $this->assertEquals('Index not found: actors', $documents['body']['message']); @@ -1461,7 +1461,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['releaseYear.equal(' . implode(',', $conditions) . ')'], + 'queries' => ['equal("releaseYear", [' . implode(',', $conditions) . '])'], ]); $this->assertEquals(400, $documents['headers']['status-code']); @@ -1476,7 +1476,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['title.search(' . implode(',', $conditions) . ')'], + 'queries' => ['search("title", ' . implode(',', $conditions) . ')'], ]); $this->assertEquals(400, $documents['headers']['status-code']); @@ -1486,7 +1486,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => ['birthDay.greater("1960-01-01 10:10:10+02:30")'], + 'queries' => ['greaterThan("birthDay", "1960-01-01 10:10:10+02:30")'], ]); $this->assertEquals($documents['headers']['status-code'], 200); diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 7b535f64dc..d1ba3b1925 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -177,7 +177,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; // Wait for deployment to be built. - sleep(5); + sleep(10); $this->assertEquals(202, $deployment['headers']['status-code']);