diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 0992f907a6..86f9d55ff0 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -174,29 +174,30 @@ App::init() App::error() ->groups(['timeout']) - ->param('databaseId', null, new UID(), 'Database ID.', true) ->inject('error') ->inject('request') ->inject('dbForProject') ->inject('utopia') - ->action(function ($databaseId, throwable $error, Request $request, Database $dbForProject, App $utopia) { + ->action(function (throwable $error, Request $request, Database $dbForProject, App $utopia) { try { $route = $utopia->match($request); if ($error instanceof Timeout) { $collectionId = $route->getParamValue('collectionId'); + $databaseId = $route->getParamValue('databaseId'); $queries = $request->getParam('queries'); + $queriesValidator = new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE); if (!$queriesValidator->isValid($queries)) { throw new Exception(Exception::GENERAL_SERVER_ERROR); } $uidValidator = new UID(); -// if (!$uidValidator->isValid($databaseId)) { -// throw new Exception(Exception::GENERAL_SERVER_ERROR, "ss", 402); -// } + if (!$uidValidator->isValid($databaseId)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR); + } if (!$uidValidator->isValid($collectionId)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, "ss", 403); + throw new Exception(Exception::GENERAL_SERVER_ERROR); } $key = getUniqueKey($request); @@ -2874,184 +2875,51 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') -App::get('/v1/databases/:databaseId/collections/:collectionId/documents/timeouts') - ->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default']) +App::get('/v1/documents/slow-queries') ->desc('List timeouts Documents') - ->groups([]) + ->groups(['api', 'database']) + ->label('docs', false) + ->label('sdk.hide', true) ->label('scope', 'documents.read') ->label('usage.metric', 'documents.{scope}.requests.read') - ->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) + //->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') - ->label('sdk.method', 'listDocuments') - ->label('sdk.description', '/docs/references/databases/list-documents.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') + ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('response') ->inject('dbForProject') - ->inject('request') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, Request $request) { - - var_dump($databaseId); - var_dump($collectionId); - // todo: check security + admin permissions? - - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { - throw new Exception(Exception::DATABASE_NOT_FOUND); + ->action(function (array $queries, Response $response, Database $dbForProject) { + $queries = Query::parseQueries($queries); + $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE); + $cursor = reset($cursor); + if ($cursor) { + $documentId = $cursor->getValue(); + $cursorDocument = Authorization::skip(fn()=>$dbForProject->getDocument('timeouts', $documentId)); + if ($cursorDocument->isEmpty()) { + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Slow query '{$documentId}' for the 'cursor' value not found."); + } + $cursor->setValue($cursorDocument); } - $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if ($collection->isEmpty()) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } - - var_dump($databaseId); - var_dump($collectionId); - - $documents = Authorization::skip(fn() => $dbForProject->find('timeouts', [ - Query::equal('blocked', [true]), - Query::equal('databaseId', [$databaseId]), - Query::equal('collectionId', [$collectionId]) - ])); - - var_dump($documents); - -// -// /** -// * Reset $collection attribute to remove prefix. -// */ -// $documents = array_map(function (Document $document) use ($collectionId, $databaseId) { -// $document->setAttribute('$collectionId', $collectionId); -// $document->setAttribute('$databaseId', $databaseId); -// return $document; -// }, $documents); - $response->dynamic(new Document([ - 'total' => count($documents), - 'documents' => $documents, + 'documents' => Authorization::skip(fn()=>$dbForProject->find('timeouts', $queries)), + 'total' => Authorization::skip(fn()=>$dbForProject->count('timeouts', Query::groupByType($queries)['filters'], APP_LIMIT_COUNT)), ]), Response::MODEL_DOCUMENT_LIST); }); -App::get('/v1/databases/:databaseId/collections/:collectionId/documents/timeouts') - ->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default']) - ->desc('List timeouts Documents') - ->groups([]) +App::get('/v1/documents/:documentId/slow-queries') + ->desc('Get Slow Query Document') + ->groups(['api', 'database']) + ->label('docs', false) + ->label('sdk.hide', true) ->label('scope', 'documents.read') ->label('usage.metric', 'documents.{scope}.requests.read') - ->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'databases') - ->label('sdk.method', 'listDocuments') - ->label('sdk.description', '/docs/references/databases/list-documents.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST) - ->param('databaseId', '', new UID(), 'Database ID.') - ->param('collectionId', '', new UID(), 'Collection ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('request') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, Request $request) { - - var_dump($databaseId); - var_dump($collectionId); - // todo: check security + admin permissions? - - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { - throw new Exception(Exception::DATABASE_NOT_FOUND); - } - - $collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId)); - if ($collection->isEmpty()) { - throw new Exception(Exception::COLLECTION_NOT_FOUND); - } - - var_dump($databaseId); - var_dump($collectionId); - - $documents = Authorization::skip(fn() => $dbForProject->find('timeouts', [ - Query::equal('blocked', [true]), - Query::equal('databaseId', [$databaseId]), - Query::equal('collectionId', [$collectionId]), - Query::limit(9999) - ])); - - var_dump($documents); - -// -// /** -// * Reset $collection attribute to remove prefix. -// */ -// $documents = array_map(function (Document $document) use ($collectionId, $databaseId) { -// $document->setAttribute('$collectionId', $collectionId); -// $document->setAttribute('$databaseId', $databaseId); -// return $document; -// }, $documents); - - $response->dynamic(new Document([ - 'total' => count($documents), - 'documents' => $documents, - ]), Response::MODEL_DOCUMENT_LIST); - }); - - - -App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId/timeouts') - ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) - ->desc('Update Document') - ->groups(['api', 'database']) - ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') - ->label('scope', 'documents.write') - ->label('audits.event', 'document.update') - ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{response.$id}') - ->label('usage.metric', 'documents.{scope}.requests.update') - ->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.namespace', 'databases') - ->label('sdk.method', 'updateDocument') - ->label('sdk.description', '/docs/references/databases/update-document.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_DOCUMENT) - ->param('documentId', '', new UID(), 'Document ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('events') - ->action(function (string $documentId, Response $response, Database $dbForProject, Event $events) { - $document = Authorization::skip(fn() => $dbForProject->getDocument('timeouts', $documentId)); - if ($document->isEmpty()) { - throw new Exception(Exception::DOCUMENT_NOT_FOUND); - } - - $document['blocked'] = false; - $document['count'] = 0; - $document = Authorization::skip(fn() => $dbForProject->updateDocument('timeouts', $document->getId(), $document)); - - $collection = $dbForProject->getCollection('timeouts'); - - $events - ->setParam('documentId', $document->getId()) - ->setContext('collection', $collection) - ; - - $response->dynamic($document, Response::MODEL_DOCUMENT); - }); - - -App::get('/v1/databases/:documentId/timeout') - ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) - ->desc('Get Document') - ->groups(['api', 'database']) - ->label('scope', 'documents.read') - ->label('usage.metric', 'documents.{scope}.requests.read') - ->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) + // ->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'getDocument') ->label('sdk.description', '/docs/references/databases/get-document.md') @@ -3064,9 +2932,6 @@ App::get('/v1/databases/:documentId/timeout') ->inject('mode') ->inject('project') ->action(function (string $documentId, Response $response, Database $dbForProject) { - - //todo not sure we need this route? - $document = Authorization::skip(fn () => $dbForProject->getDocument('timeouts', $documentId)); if ($document->isEmpty()) { throw new Exception(Exception::DOCUMENT_NOT_FOUND); @@ -3074,3 +2939,53 @@ App::get('/v1/databases/:documentId/timeout') $response->dynamic($document, Response::MODEL_DOCUMENT); }); + + + + +App::delete('/v1/documents/:documentId/slow-queries') + ->desc('Delete Slow query Document') + ->desc('List Documents') + ->groups(['api', 'database']) + ->label('docs', false) + ->label('sdk.hide', true) + ->label('scope', 'documents.write') + ->label('usage.metric', 'documents.{scope}.requests.delete') + //->label('usage.params', ['databaseId:{request.databaseId}', 'collectionId:{request.collectionId}']) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'databases') + ->label('event', 'timeouts.documents.[documentId].delete') + ->label('audits.event', 'document.delete') + ->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}/document/{request.documentId}') + ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('documentId', '', new UID(), 'Document ID.') + ->inject('response') + ->inject('dbForProject') + ->inject('events') + ->inject('deletes') + ->action(function (string $documentId, Response $response, Database $dbForProject, Event $events, Delete $deletes) { + $document = Authorization::skip(fn() => $dbForProject->getDocument('timeouts', $documentId)); + if ($document->isEmpty()) { + throw new Exception(Exception::DOCUMENT_NOT_FOUND); + } + + Authorization::skip(fn() => $dbForProject->deleteDocument('timeouts', $documentId)); + + $dbForProject->deleteCachedDocument('timeouts', $documentId); + + $deletes + ->setType(DELETE_TYPE_AUDIT) + ->setDocument($document) + ; + + // todo: check events + + $events + ->setParam('collectionId', 'timeouts') + ->setParam('documentId', $document->getId()) + ->setPayload($response->output($document, Response::MODEL_DOCUMENT)) + ; + + $response->noContent(); + }); diff --git a/composer.lock b/composer.lock index c12c2a836c..b2afbecdb9 100644 --- a/composer.lock +++ b/composer.lock @@ -2121,12 +2121,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "bd569398141b8e2098066ef2958a5038581a836f" + "reference": "f30419c03ec98b2849e1504fdcb99ab771609e39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/bd569398141b8e2098066ef2958a5038581a836f", - "reference": "bd569398141b8e2098066ef2958a5038581a836f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/f30419c03ec98b2849e1504fdcb99ab771609e39", + "reference": "f30419c03ec98b2849e1504fdcb99ab771609e39", "shasum": "" }, "require": { @@ -2168,7 +2168,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/timeout" }, - "time": "2023-01-17T13:42:38+00:00" + "time": "2023-01-25T11:25:18+00:00" }, { "name": "utopia-php/domains", diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index c7540e8a84..2d83a293df 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -1017,20 +1017,6 @@ trait DatabasesBase } - /** - * @depends testCreateDocument - */ - public function testTimeoutDocuments(array $data): void - { - $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['moviesId'] . '/documents/timeouts', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); -var_dump($documents); - $this->assertEquals(20000, $documents['headers']['status-code']); - } - - /** * @depends testCreateDocument */ @@ -1044,12 +1030,34 @@ var_dump($documents); ]); $this->assertEquals(403, $documents['headers']['status-code']); + } + + + /** + * @depends testCreateDocument + */ + public function testConsoletimeouts(array $data): void + { + $documents = $this->client->call(Client::METHOD_GET, '/documents/slow-queries', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'equal("blocked", true)', + 'equal("databaseId", "' . $data['databaseId'] . '")', + 'equal("collectionId", "' . $data['moviesId'] . '")' + ], + ]); + + var_dump($documents); + + $this->assertEquals(20000, $documents['headers']['status-code']); exit; - - } + + /** * @depends testCreateDocument */