diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index ac14421382..3c840094ec 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -51,6 +51,15 @@ return [ 'default' => null, 'array' => false, ], + [ + '$id' => ID::custom('type'), + 'type' => Database::VAR_STRING, + 'size' => 128, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ diff --git a/composer.json b/composer.json index 6deb88c878..4f3139a974 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,7 @@ "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.18.*", - "utopia-php/migration": "0.13.*", + "utopia-php/migration": "0.14.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.7.*", "utopia-php/pools": "0.8.*", diff --git a/composer.lock b/composer.lock index bdc4997be8..c8b4ad7ee8 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": "b6ec099ac43f7f5955b3f7d54ef21764", + "content-hash": "ced183858d8fde850249c5c34f6f1b73", "packages": [ { "name": "adhocore/jwt", @@ -4042,16 +4042,16 @@ }, { "name": "utopia-php/migration", - "version": "0.13.7", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "fc25d50c3a19e701e905c56a9465143cacb02717" + "reference": "f9a7e87413b82975dbd87b7850aec2543aeac7ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/fc25d50c3a19e701e905c56a9465143cacb02717", - "reference": "fc25d50c3a19e701e905c56a9465143cacb02717", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/f9a7e87413b82975dbd87b7850aec2543aeac7ee", + "reference": "f9a7e87413b82975dbd87b7850aec2543aeac7ee", "shasum": "" }, "require": { @@ -4092,9 +4092,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.13.7" + "source": "https://github.com/utopia-php/migration/tree/0.14.0" }, - "time": "2025-07-31T15:08:29+00:00" + "time": "2025-08-05T12:33:09+00:00" }, { "name": "utopia-php/orchestration", diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index d47490e604..2d82b9c486 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -89,6 +89,7 @@ abstract class Migration '1.7.2' => 'V22', '1.7.3' => 'V22', '1.7.4' => 'V22', + '1.8.0' => 'V23' ]; /** diff --git a/src/Appwrite/Migration/Version/V23.php b/src/Appwrite/Migration/Version/V23.php new file mode 100644 index 0000000000..88f6d0451b --- /dev/null +++ b/src/Appwrite/Migration/Version/V23.php @@ -0,0 +1,52 @@ + null, + fn () => [] + ); + } + + Console::info('Migrating databases'); + $this->migrateDatabases(); + } + + /** + * Migrate Databases. + * + * @return void + * @throws Exception|Throwable + */ + private function migrateDatabases(): void + { + if ($this->project->getId() === 'console') { + return; + } + + // since required + default can't be used together + // so first creating the attribute then bulk updating the attribute + $this->createAttributeFromCollection($this->dbForProject, 'databases', 'type'); + $this->dbForProject->updateDocuments('databases', new Document(['type' => 'sql'])); + } + +} diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php index 9b52b482bb..e5256deb3b 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Create.php @@ -23,6 +23,7 @@ use Utopia\Platform\Action; use Utopia\Swoole\Response as SwooleResponse; use Utopia\Validator\Boolean; use Utopia\Validator\Text; +use Utopia\Validator\WhiteList; class Create extends Action { @@ -80,13 +81,14 @@ class Create extends Action ->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), 'Database name. Max length: 128 chars.') ->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true) + ->param('type', 'sql', new WhiteList(['sql','nosql']), 'Database type.', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') ->callback($this->action(...)); } - public function action(string $databaseId, string $name, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void + public function action(string $databaseId, string $name, bool $enabled, string $type, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void { $databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId; @@ -96,6 +98,7 @@ class Create extends Action 'name' => $name, 'enabled' => $enabled, 'search' => implode(' ', [$databaseId, $name]), + 'type' => $type ])); } catch (DuplicateException) { throw new Exception(Exception::DATABASE_ALREADY_EXISTS); diff --git a/src/Appwrite/Utopia/Response/Model/Database.php b/src/Appwrite/Utopia/Response/Model/Database.php index 90b4ac8cb4..a9ce20da74 100644 --- a/src/Appwrite/Utopia/Response/Model/Database.php +++ b/src/Appwrite/Utopia/Response/Model/Database.php @@ -40,6 +40,12 @@ class Database extends Model 'default' => true, 'example' => false, ]) + ->addRule('type', [ + 'type' => self::TYPE_STRING, + 'description' => 'Database type.', + 'default' => 'sql', + 'example' => 'sql', + ]) ; } diff --git a/tests/e2e/Services/Databases/Grids/DatabasesBase.php b/tests/e2e/Services/Databases/Grids/DatabasesBase.php index 77c5a958ac..2e0f7bc6ea 100644 --- a/tests/e2e/Services/Databases/Grids/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Grids/DatabasesBase.php @@ -32,6 +32,44 @@ trait DatabasesBase $this->assertNotEmpty($database['body']['$id']); $this->assertEquals(201, $database['headers']['status-code']); $this->assertEquals('Test Database', $database['body']['name']); + $this->assertEquals('sql', $database['body']['type']); + + // testing to create a database with type + $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database with type', + 'type' => 'mongodb' + ]); + $this->assertEquals(400, $database2['headers']['status-code']); + + $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database with type', + 'type' => 'nosql' + ]); + $this->assertNotEmpty($database2['body']['$id']); + $this->assertEquals(201, $database2['headers']['status-code']); + $this->assertEquals('Test Database with type', $database2['body']['name']); + $this->assertEquals('nosql', $database2['body']['type']); + + // cleanup(for database2) + $databaseId = $database2['body']['$id']; + + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]); + + $this->assertEquals(204, $response['headers']['status-code']); return ['databaseId' => $database['body']['$id']]; } diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 958b2695b0..449d3621c5 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -32,6 +32,44 @@ trait DatabasesBase $this->assertNotEmpty($database['body']['$id']); $this->assertEquals(201, $database['headers']['status-code']); $this->assertEquals('Test Database', $database['body']['name']); + $this->assertEquals('sql', $database['body']['type']); + + // testing to create a database with type + $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database with type', + 'type' => 'mongodb' + ]); + $this->assertEquals(400, $database2['headers']['status-code']); + + $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database with type', + 'type' => 'nosql' + ]); + $this->assertNotEmpty($database2['body']['$id']); + $this->assertEquals(201, $database2['headers']['status-code']); + $this->assertEquals('Test Database with type', $database2['body']['name']); + $this->assertEquals('nosql', $database2['body']['type']); + + // cleanup(for database2) + $databaseId = $database2['body']['$id']; + + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]); + + $this->assertEquals(204, $response['headers']['status-code']); return ['databaseId' => $database['body']['$id']]; }