diff --git a/app/config/collections.php b/app/config/collections.php index 0b4dda6851..85a279faa5 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -32,6 +32,17 @@ $collections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('enabled'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => false, + 'default' => true, + 'array' => false, + ], [ '$id' => ID::custom('search'), 'type' => Database::VAR_STRING, diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 8a18e28dba..9eedbb986c 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -384,11 +384,12 @@ App::post('/v1/databases') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DATABASE) // Model for database needs to be created ->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') - ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') + ->param('name', '', new Text(128), 'Database name. Max length: 128 chars.') + ->param('enabled', true, new Boolean(), 'Is database enabled?', true) ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $databaseId, string $name, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $events) { $databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId; @@ -396,6 +397,7 @@ App::post('/v1/databases') $dbForProject->createDocument('databases', new Document([ '$id' => $databaseId, 'name' => $name, + 'enabled' => $enabled, 'search' => implode(' ', [$databaseId, $name]), ])); $database = $dbForProject->getDocument('databases', $databaseId); @@ -507,7 +509,7 @@ App::get('/v1/databases/:databaseId') ->inject('dbForProject') ->action(function (string $databaseId, Response $response, Database $dbForProject) { - $database = $dbForProject->getDocument('databases', $databaseId); + $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -619,12 +621,13 @@ App::put('/v1/databases/:databaseId') ->label('sdk.response.model', Response::MODEL_DATABASE) ->param('databaseId', '', new UID(), 'Database ID.') ->param('name', null, new Text(128), 'Database name. Max length: 128 chars.') + ->param('enabled', true, new Boolean(), 'Is database enabled?', true) ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $databaseId, string $name, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $databaseId, string $name, bool $enabled, Response $response, Database $dbForProject, Event $events) { - $database = $dbForProject->getDocument('databases', $databaseId); + $database = $dbForProject->getDocument('databases', $databaseId); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -633,6 +636,7 @@ App::put('/v1/databases/:databaseId') try { $database = $dbForProject->updateDocument('databases', $databaseId, $database ->setAttribute('name', $name) + ->setAttribute('enabled', $enabled) ->setAttribute('search', implode(' ', [$databaseId, $name]))); } catch (AuthorizationException $exception) { throw new Exception(Exception::USER_UNAUTHORIZED); @@ -714,14 +718,16 @@ App::post('/v1/databases/:databaseId/collections') ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).', true) ->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](/docs/permissions).', true) + ->param('enabled', true, new Boolean(), 'Is collection enabled?', true) ->inject('response') ->inject('dbForProject') + ->inject('mode') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $events) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -737,7 +743,7 @@ App::post('/v1/databases/:databaseId/collections') 'databaseId' => $databaseId, '$permissions' => $permissions ?? [], 'documentSecurity' => $documentSecurity, - 'enabled' => true, + 'enabled' => $enabled, 'name' => $name, 'search' => implode(' ', [$collectionId, $name]), ])); @@ -779,11 +785,12 @@ App::get('/v1/databases/:databaseId/collections') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject) { + ->inject('mode') + ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -834,11 +841,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') ->param('collectionId', '', new UID(), 'Collection ID.') ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject) { + ->inject('mode') + ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -874,7 +882,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->inject('geodb') ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -972,12 +980,13 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') ->param('enabled', true, new Boolean(), 'Is collection enabled?', true) ->inject('response') ->inject('dbForProject') + ->inject('mode') ->inject('events') - ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, Event $events) { + ->action(function (string $databaseId, string $collectionId, string $name, ?array $permissions, bool $documentSecurity, bool $enabled, Response $response, Database $dbForProject, string $mode, Event $events) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -1036,13 +1045,14 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') ->param('collectionId', '', new UID(), 'Collection ID.') ->inject('response') ->inject('dbForProject') + ->inject('mode') ->inject('events') ->inject('deletes') - ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, Event $events, Delete $deletes) { + ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject, string $mode, Event $events, Delete $deletes) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -1060,15 +1070,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') $deletes ->setType(DELETE_TYPE_DOCUMENT) - ->setDocument($collection) - ; + ->setDocument($collection); $events ->setContext('database', $database) ->setParam('databaseId', $databaseId) ->setParam('collectionId', $collection->getId()) - ->setPayload($response->output($collection, Response::MODEL_COLLECTION)) - ; + ->setPayload($response->output($collection, Response::MODEL_COLLECTION)); $response->noContent(); }); @@ -1593,7 +1601,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati Database $dbForProject, EventDatabase $database, Event $events -) { + ) { $key ??= $relatedCollectionId; $twoWayKey ??= $collectionId; @@ -1653,7 +1661,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') ->inject('dbForProject') ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -1704,7 +1712,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') ->inject('dbForProject') ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2177,7 +2185,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ $events, type: Database::VAR_RELATIONSHIP, required: false, - options : [ + options: [ 'onDelete' => $onDelete ] ); @@ -2218,7 +2226,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->inject('events') ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2271,8 +2279,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->setType(DATABASE_TYPE_DELETE_ATTRIBUTE) ->setCollection($collection) ->setDatabase($db) - ->setDocument($attribute) - ; + ->setDocument($attribute); // Select response model based on type and format $type = $attribute->getAttribute('type'); @@ -2300,8 +2307,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key ->setParam('attributeId', $attribute->getId()) ->setContext('collection', $collection) ->setContext('database', $db) - ->setPayload($response->output($attribute, $model)) - ; + ->setPayload($response->output($attribute, $model)); $response->noContent(); }); @@ -2335,7 +2341,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->inject('events') ->action(function (string $databaseId, string $collectionId, string $key, string $type, array $attributes, array $orders, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2358,7 +2364,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') } // Convert Document[] to array of attribute metadata - $oldAttributes = \array_map(fn ($a) => $a->getArrayCopy(), $collection->getAttribute('attributes')); + $oldAttributes = \array_map(fn($a) => $a->getArrayCopy(), $collection->getAttribute('attributes')); $oldAttributes[] = [ 'key' => '$id', @@ -2444,16 +2450,14 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') ->setType(DATABASE_TYPE_CREATE_INDEX) ->setDatabase($db) ->setCollection($collection) - ->setDocument($index) - ; + ->setDocument($index); $events ->setParam('databaseId', $databaseId) ->setParam('collectionId', $collection->getId()) ->setParam('indexId', $index->getId()) ->setContext('collection', $collection) - ->setContext('database', $db) - ; + ->setContext('database', $db); $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) @@ -2480,7 +2484,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') ->inject('dbForProject') ->action(function (string $databaseId, string $collectionId, Response $response, Database $dbForProject) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2520,7 +2524,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->inject('dbForProject') ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2571,7 +2575,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->inject('events') ->action(function (string $databaseId, string $collectionId, string $key, Response $response, Database $dbForProject, EventDatabase $database, Event $events) { - $db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $db = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($db->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -2599,8 +2603,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->setType(DATABASE_TYPE_DELETE_INDEX) ->setDatabase($db) ->setCollection($collection) - ->setDocument($index) - ; + ->setDocument($index); $events ->setParam('databaseId', $databaseId) @@ -2608,8 +2611,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/indexes/:key') ->setParam('indexId', $index->getId()) ->setContext('collection', $collection) ->setContext('database', $db) - ->setPayload($response->output($index, Response::MODEL_INDEX)) - ; + ->setPayload($response->output($index, Response::MODEL_INDEX)); $response->noContent(); }); @@ -2660,7 +2662,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -2735,7 +2737,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -2814,7 +2816,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -2847,8 +2849,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->setParam('collectionId', $collection->getId()) ->setParam('documentId', $document->getId()) ->setContext('collection', $collection) - ->setContext('database', $database) - ; + ->setContext('database', $database); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -2878,9 +2879,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('mode') ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -2940,7 +2941,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -2956,8 +2957,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - $relatedCollection = Authorization::skip(fn() => - $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); + $relatedCollection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); foreach ($relations as $index => $doc) { if ($doc instanceof Document) { @@ -2977,7 +2977,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') return true; }; - // The linter is forcing this indentation + // The linter is forcing this indentation foreach ($documents as $document) { $processDocument($collection, $document); } @@ -3013,9 +3013,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->inject('mode') ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -3053,7 +3053,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -3108,7 +3108,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->inject('geodb') ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); if ($database->isEmpty()) { throw new Exception(Exception::DATABASE_NOT_FOUND); @@ -3225,9 +3225,9 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD); } - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -3304,7 +3304,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -3372,7 +3372,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum try { $document = $dbForProject->withRequestTimestamp( $requestTimestamp, - fn () => $dbForProject->updateDocument( + fn() => $dbForProject->updateDocument( 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document->getId(), $newDocument @@ -3393,7 +3393,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -3426,8 +3426,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->setParam('collectionId', $collection->getId()) ->setParam('documentId', $document->getId()) ->setContext('collection', $collection) - ->setContext('database', $database) - ; + ->setContext('database', $database); $response->dynamic($document, Response::MODEL_DOCUMENT); }); @@ -3464,9 +3463,9 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->inject('mode') ->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events, Delete $deletes, string $mode) { - $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); + $database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId)); - if ($database->isEmpty()) { + if ($database->isEmpty() || (!$database->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) { throw new Exception(Exception::DATABASE_NOT_FOUND); } @@ -3501,7 +3500,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -3532,7 +3531,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $checkPermissions($collection, $document); - Authorization::skip(fn () => $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { + Authorization::skip(fn() => $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { try { $dbForProject->deleteDocument( 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), @@ -3555,7 +3554,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $relationships = \array_filter( $collection->getAttribute('attributes', []), - fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP + fn($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); foreach ($relationships as $relationship) { @@ -3585,8 +3584,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $deletes ->setType(DELETE_TYPE_AUDIT) - ->setDocument($document) - ; + ->setDocument($document); $events ->setParam('databaseId', $databaseId) @@ -3594,8 +3592,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->setParam('documentId', $document->getId()) ->setContext('collection', $collection) ->setContext('database', $database) - ->setPayload($response->output($document, Response::MODEL_DOCUMENT)) - ; + ->setPayload($response->output($document, Response::MODEL_DOCUMENT)); $response->noContent(); }); @@ -3700,16 +3697,16 @@ App::get('/v1/databases/usage') 'databasesCount' => $stats['databases.$all.count.total'] ?? [], 'documentsCount' => $stats['documents.$all.count.total'] ?? [], 'collectionsCount' => $stats['collections.$all.count.total'] ?? [], - 'documentsCreate' => $stats['documents.$all.requests.create'] ?? [], - 'documentsRead' => $stats['documents.$all.requests.read'] ?? [], + 'documentsCreate' => $stats['documents.$all.requests.create'] ?? [], + 'documentsRead' => $stats['documents.$all.requests.read'] ?? [], 'documentsUpdate' => $stats['documents.$all.requests.update'] ?? [], 'documentsDelete' => $stats['documents.$all.requests.delete'] ?? [], 'collectionsCreate' => $stats['collections.$all.requests.create'] ?? [], - 'collectionsRead' => $stats['collections.$all.requests.read'] ?? [], + 'collectionsRead' => $stats['collections.$all.requests.read'] ?? [], 'collectionsUpdate' => $stats['collections.$all.requests.update'] ?? [], 'collectionsDelete' => $stats['collections.$all.requests.delete'] ?? [], 'databasesCreate' => $stats['databases.$all.requests.create'] ?? [], - 'databasesRead' => $stats['databases.$all.requests.read'] ?? [], + 'databasesRead' => $stats['databases.$all.requests.read'] ?? [], 'databasesUpdate' => $stats['databases.$all.requests.update'] ?? [], 'databasesDelete' => $stats['databases.$all.requests.delete'] ?? [], ]); @@ -3813,12 +3810,12 @@ App::get('/v1/databases/:databaseId/usage') 'range' => $range, 'collectionsCount' => $stats["collections.{$databaseId}.count.total"] ?? [], 'collectionsCreate' => $stats["collections.{$databaseId}.requests.create"] ?? [], - 'collectionsRead' => $stats["collections.{$databaseId}.requests.read"] ?? [], + 'collectionsRead' => $stats["collections.{$databaseId}.requests.read"] ?? [], 'collectionsUpdate' => $stats["collections.{$databaseId}.requests.update"] ?? [], 'collectionsDelete' => $stats["collections.{$databaseId}.requests.delete"] ?? [], 'documentsCount' => $stats["documents.{$databaseId}.count.total"] ?? [], - 'documentsCreate' => $stats["documents.{$databaseId}.requests.create"] ?? [], - 'documentsRead' => $stats["documents.{$databaseId}.requests.read"] ?? [], + 'documentsCreate' => $stats["documents.{$databaseId}.requests.create"] ?? [], + 'documentsRead' => $stats["documents.{$databaseId}.requests.read"] ?? [], 'documentsUpdate' => $stats["documents.{$databaseId}.requests.update"] ?? [], 'documentsDelete' => $stats["documents.{$databaseId}.requests.delete"] ?? [], ]); @@ -3928,8 +3925,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage') 'documentsCount' => $stats["documents.{$databaseId}/{$collectionId}.count.total"] ?? [], 'documentsCreate' => $stats["documents.{$databaseId}/{$collectionId}.requests.create"] ?? [], 'documentsRead' => $stats["documents.{$databaseId}/{$collectionId}.requests.read"] ?? [], - 'documentsUpdate' => $stats["documents.{$databaseId}/{$collectionId}.requests.update"] ?? [], - 'documentsDelete' => $stats["documents.{$databaseId}/{$collectionId}.requests.delete" ?? []] + 'documentsUpdate' => $stats["documents.{$databaseId}/{$collectionId}.requests.update"] ?? [], + 'documentsDelete' => $stats["documents.{$databaseId}/{$collectionId}.requests.delete" ?? []] ]); } diff --git a/composer.lock b/composer.lock index 2bb32246f0..6f5b086b9c 100644 --- a/composer.lock +++ b/composer.lock @@ -481,16 +481,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.5.1", + "version": "7.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" + "reference": "8444a2bacf1960bc6a2b62ed86b8e72e11eebe51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", - "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8444a2bacf1960bc6a2b62ed86b8e72e11eebe51", + "reference": "8444a2bacf1960bc6a2b62ed86b8e72e11eebe51", "shasum": "" }, "require": { @@ -521,9 +521,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" } }, "autoload": { @@ -589,7 +586,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.1" + "source": "https://github.com/guzzle/guzzle/tree/7.6.1" }, "funding": [ { @@ -605,7 +602,7 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:30:08+00:00" + "time": "2023-05-15T20:43:01+00:00" }, { "name": "guzzlehttp/promises", @@ -3784,16 +3781,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.20.3", + "version": "1.20.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2" + "reference": "7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2", - "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd", + "reference": "7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd", "shasum": "" }, "require": { @@ -3823,9 +3820,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.4" }, - "time": "2023-04-25T09:01:03+00:00" + "time": "2023-05-02T09:19:37+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4548,16 +4545,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -4602,7 +4599,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -4610,7 +4607,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", @@ -5577,16 +5574,16 @@ }, { "name": "twig/twig", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" + "reference": "106c170d08e8415d78be2d16c3d057d0d108262b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/106c170d08e8415d78be2d16c3d057d0d108262b", + "reference": "106c170d08e8415d78be2d16c3d057d0d108262b", "shasum": "" }, "require": { @@ -5595,15 +5592,10 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "psr/container": "^1.0", + "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } - }, "autoload": { "psr-4": { "Twig\\": "src/" @@ -5637,7 +5629,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.5.1" + "source": "https://github.com/twigphp/Twig/tree/v3.6.0" }, "funding": [ { @@ -5649,7 +5641,7 @@ "type": "tidelift" } ], - "time": "2023-02-08T07:49:20+00:00" + "time": "2023-05-03T19:06:57+00:00" } ], "aliases": [], diff --git a/src/Appwrite/Utopia/Response/Model/Database.php b/src/Appwrite/Utopia/Response/Model/Database.php index 9d9a4b2762..bd9ae4625c 100644 --- a/src/Appwrite/Utopia/Response/Model/Database.php +++ b/src/Appwrite/Utopia/Response/Model/Database.php @@ -34,6 +34,12 @@ class Database extends Model 'default' => '', 'example' => self::TYPE_DATETIME_EXAMPLE, ]) + ->addRule('enabled', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Database enabled.', + 'default' => true, + 'example' => false, + ]) ; } diff --git a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php index ad97a2bb3e..13d28df5f3 100644 --- a/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php +++ b/tests/e2e/Services/Databases/DatabasesConsoleClientTest.php @@ -20,13 +20,13 @@ class DatabasesConsoleClientTest extends Scope $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ + ], $this->getHeaders()), [ 'databaseId' => ID::unique(), 'name' => 'invalidDocumentDatabase', ]); $this->assertEquals(201, $database['headers']['status-code']); $this->assertEquals('invalidDocumentDatabase', $database['body']['name']); + $this->assertTrue($database['body']['enabled']); $databaseId = $database['body']['$id']; /** @@ -50,7 +50,129 @@ class DatabasesConsoleClientTest extends Scope $this->assertEquals(201, $movies['headers']['status-code']); $this->assertEquals($movies['body']['name'], 'Movies'); - return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId]; + /** + * Test When database is disabled but can still create collections + */ + $database = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'invalidDocumentDatabase Updated', + 'enabled' => false, + ]); + + $this->assertFalse($database['body']['enabled']); + + $tvShows = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'collectionId' => ID::unique(), + 'name' => 'TvShows', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'documentSecurity' => true, + ]); + + $this->assertEquals(201, $tvShows['headers']['status-code']); + $this->assertEquals($tvShows['body']['name'], 'TvShows'); + + return ['moviesId' => $movies['body']['$id'], 'databaseId' => $databaseId, 'tvShowsId' => $tvShows['body']['$id']]; + } + + /** + * @depends testCreateCollection + * @param array $data + */ + public function testListCollection(array $data) + { + /** + * Test When database is disabled but can still call list collections + */ + $databaseId = $data['databaseId']; + + $collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], $this->getHeaders())); + + $this->assertEquals(200, $collections['headers']['status-code']); + $this->assertEquals(2, $collections['body']['total']); + } + + /** + * @depends testCreateCollection + * @param array $data + */ + public function testGetCollection(array $data) + { + $databaseId = $data['databaseId']; + $moviesCollectionId = $data['moviesId']; + + /** + * Test When database is disabled but can still call get collection + */ + $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertEquals('Movies', $collection['body']['name']); + $this->assertEquals($moviesCollectionId, $collection['body']['$id']); + $this->assertTrue($collection['body']['enabled']); + } + + /** + * @depends testCreateCollection + * @param array $data + */ + public function testUpdateCollection(array $data) + { + $databaseId = $data['databaseId']; + $moviesCollectionId = $data['moviesId']; + + /** + * Test When database is disabled but can still call update collection + */ + $collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $moviesCollectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Movies Updated', + 'enabled' => false + ]); + + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertEquals('Movies Updated', $collection['body']['name']); + $this->assertEquals($moviesCollectionId, $collection['body']['$id']); + $this->assertFalse($collection['body']['enabled']); + } + + /** + * @depends testCreateCollection + * @param array $data + */ + public function testDeleteCollection(array $data) + { + $databaseId = $data['databaseId']; + $tvShowsId = $data['tvShowsId']; + + /** + * Test When database is disabled but can still call Delete collection + */ + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $tvShowsId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEquals($response['body'], ""); } /** diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index ce6602c176..5e734fbcb2 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -27,6 +27,7 @@ class DatabasesCustomServerTest extends Scope 'databaseId' => ID::custom('first'), 'name' => 'Test 1', ]); + $this->assertEquals(201, $test1['headers']['status-code']); $this->assertEquals('Test 1', $test1['body']['name']); @@ -56,7 +57,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ], + 'queries' => ['limit(1)'], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -65,7 +66,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ], + 'queries' => ['offset(1)'], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -74,7 +75,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("name", ["Test 1", "Test 2"])' ], + 'queries' => ['equal("name", ["Test 1", "Test 2"])'], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(2, $databases['body']['databases']); @@ -83,7 +84,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("name", "Test 2")' ], + 'queries' => ['equal("name", "Test 2")'], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -92,7 +93,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("$id", "first")' ], + 'queries' => ['equal("$id", "first")'], ]); $this->assertEquals(200, $databases['headers']['status-code']); $this->assertCount(1, $databases['body']['databases']); @@ -104,7 +105,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'orderDesc("")' ], + 'queries' => ['orderDesc("")'], ]); $this->assertEquals(2, $databases['body']['total']); @@ -123,7 +124,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $base['body']['databases'][0]['$id'] . '")' ], + 'queries' => ['cursorAfter("' . $base['body']['databases'][0]['$id'] . '")'], ]); $this->assertCount(1, $databases['body']['databases']); @@ -133,7 +134,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $base['body']['databases'][1]['$id'] . '")' ], + 'queries' => ['cursorAfter("' . $base['body']['databases'][1]['$id'] . '")'], ]); $this->assertCount(0, $databases['body']['databases']); @@ -151,7 +152,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorBefore("' . $base['body']['databases'][1]['$id'] . '")' ], + 'queries' => ['cursorBefore("' . $base['body']['databases'][1]['$id'] . '")'], ]); $this->assertCount(1, $databases['body']['databases']); @@ -161,7 +162,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorBefore("' . $base['body']['databases'][0]['$id'] . '")' ], + 'queries' => ['cursorBefore("' . $base['body']['databases'][0]['$id'] . '")'], ]); $this->assertCount(0, $databases['body']['databases']); @@ -207,7 +208,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("unknown")' ], + 'queries' => ['cursorAfter("unknown")'], ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -244,10 +245,44 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(200, $database['headers']['status-code']); $this->assertEquals($databaseId, $database['body']['$id']); $this->assertEquals('Test 1', $database['body']['name']); - + $this->assertEquals(true, $database['body']['enabled']); return ['databaseId' => $database['body']['$id']]; } + /** + * @depends testListDatabases + */ + public function testUpdateDatabase(array $data) + { + $databaseId = $data['databaseId']; + + $database = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'name' => 'Test 1 Updated', + 'enabled' => false, + ]); + + $this->assertEquals(200, $database['headers']['status-code']); + $this->assertEquals('Test 1 Updated', $database['body']['name']); + $this->assertFalse($database['body']['enabled']); + + // Now update the database without the passing the enabled parameter + $database = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'name' => 'Test 1' + ]); + + $this->assertEquals(200, $database['headers']['status-code']); + $this->assertEquals('Test 1', $database['body']['name']); + $this->assertTrue($database['body']['enabled']); + } + /** * @depends testListDatabases */ @@ -273,7 +308,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); } - public function testListCollections() + public function testListCollections(): array { $database = $this->client->call(Client::METHOD_POST, '/databases', array_merge([ 'content-type' => 'application/json', @@ -285,6 +320,7 @@ class DatabasesCustomServerTest extends Scope ]); $this->assertEquals(201, $database['headers']['status-code']); $this->assertEquals('invalidDocumentDatabase', $database['body']['name']); + $this->assertTrue($database['body']['enabled']); $databaseId = $database['body']['$id']; /** @@ -329,7 +365,9 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(2, $collections['body']['total']); $this->assertEquals($test1['body']['$id'], $collections['body']['collections'][0]['$id']); + $this->assertEquals($test1['body']['enabled'], $collections['body']['collections'][0]['enabled']); $this->assertEquals($test2['body']['$id'], $collections['body']['collections'][1]['$id']); + $this->assertEquals($test1['body']['enabled'], $collections['body']['collections'][0]['enabled']); $base = array_reverse($collections['body']['collections']); @@ -337,7 +375,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'limit(1)' ] + 'queries' => ['limit(1)'] ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -347,7 +385,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'offset(1)' ] + 'queries' => ['offset(1)'] ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -357,7 +395,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("enabled", true)' ] + 'queries' => ['equal("enabled", true)'] ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -367,7 +405,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'equal("enabled", false)' ] + 'queries' => ['equal("enabled", false)'] ]); $this->assertEquals(200, $collections['headers']['status-code']); @@ -380,7 +418,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'orderDesc("")' ], + 'queries' => ['orderDesc("")'], ]); $this->assertEquals(2, $collections['body']['total']); @@ -399,7 +437,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $base['body']['collections'][0]['$id'] . '")' ], + 'queries' => ['cursorAfter("' . $base['body']['collections'][0]['$id'] . '")'], ]); $this->assertCount(1, $collections['body']['collections']); @@ -409,7 +447,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("' . $base['body']['collections'][1]['$id'] . '")' ], + 'queries' => ['cursorAfter("' . $base['body']['collections'][1]['$id'] . '")'], ]); $this->assertCount(0, $collections['body']['collections']); @@ -427,7 +465,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorBefore("' . $base['body']['collections'][1]['$id'] . '")' ], + 'queries' => ['cursorBefore("' . $base['body']['collections'][1]['$id'] . '")'], ]); $this->assertCount(1, $collections['body']['collections']); @@ -437,7 +475,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorBefore("' . $base['body']['collections'][0]['$id'] . '")' ], + 'queries' => ['cursorBefore("' . $base['body']['collections'][0]['$id'] . '")'], ]); $this->assertCount(0, $collections['body']['collections']); @@ -483,7 +521,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'queries' => [ 'cursorAfter("unknown")' ], + 'queries' => ['cursorAfter("unknown")'], ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -506,6 +544,53 @@ class DatabasesCustomServerTest extends Scope ]); $this->assertEquals(409, $response['headers']['status-code']); + return [ + 'databaseId' => $databaseId, + 'collectionId' => $test1['body']['$id'], + ]; + } + + /** + * @depends testListCollections + */ + public function testGetCollection(array $data): void + { + $databaseId = $data['databaseId']; + $collectionId = $data['collectionId']; + + $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], $this->getHeaders())); + + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertEquals('Test 1', $collection['body']['name']); + $this->assertEquals('first', $collection['body']['$id']); + $this->assertTrue($collection['body']['enabled']); + } + + /** + * @depends testListCollections + */ + public function testUpdateCollection(array $data) + { + $databaseId = $data['databaseId']; + $collectionId = $data['collectionId']; + + $collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Test 1 Updated', + 'enabled' => false + ]); + + $this->assertEquals(200, $collection['headers']['status-code']); + $this->assertEquals('Test 1 Updated', $collection['body']['name']); + $this->assertEquals('first', $collection['body']['$id']); + $this->assertFalse($collection['body']['enabled']); } public function testDeleteAttribute(): array @@ -589,7 +674,7 @@ class DatabasesCustomServerTest extends Scope 'data' => [ 'firstName' => 'lorem', 'lastName' => 'ipsum', - 'unneeded' => 'dolor' + 'unneeded' => 'dolor' ], 'permissions' => [ Permission::read(Role::any()), @@ -1432,7 +1517,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals('lorem', $attribute['default']); @@ -1574,7 +1659,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals('torsten@appwrite.io', $attribute['default']); @@ -1717,7 +1802,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals('127.0.0.1', $attribute['default']); @@ -1859,7 +1944,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals('http://appwrite.io', $attribute['default']); @@ -2005,7 +2090,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals(123, $attribute['default']); @@ -2268,7 +2353,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals(123.456, $attribute['default']); @@ -2527,7 +2612,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals(true, $attribute['default']); @@ -2669,7 +2754,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals('1975-06-12 14:12:55+02:00', $attribute['default']); @@ -2816,7 +2901,7 @@ class DatabasesCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - $attribute = array_values(array_filter($new['body']['attributes'], fn (array $a) => $a['key'] === $key))[0] ?? null; + $attribute = array_values(array_filter($new['body']['attributes'], fn(array $a) => $a['key'] === $key))[0] ?? null; $this->assertNotNull($attribute); $this->assertFalse($attribute['required']); $this->assertEquals('lorem', $attribute['default']);