From a895ce4270099e9184d201a6c4c9d2951d09b7fb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 May 2024 16:51:29 +1200 Subject: [PATCH 01/11] Remove redundant backup checks --- app/controllers/api/projects.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 5464014b48..c07c708bd4 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -111,35 +111,8 @@ App::post('/v1/projects') $projectId = ($projectId == 'unique()') ? ID::unique() : $projectId; - $backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '05:00']; - $backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00']; - $databases = Config::getParam('pools-database', []); - /** - * Remove databases from the list that are currently undergoing an backup - */ - if (count($databases) > 1) { - $now = new \DateTime(); - - foreach ($databases as $index => $database) { - if (empty($backups[$database])) { - continue; - } - $backup = $backups[$database]; - $from = \DateTime::createFromFormat('H:i', $backup['from']); - $to = \DateTime::createFromFormat('H:i', $backup['to']); - if ($now >= $from && $now <= $to) { - unset($databases[$index]); - break; - } - } - } - $databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE'); $index = \array_search($databaseOverride, $databases); if ($index !== false) { From 4ed125f7c880c6e8ec7dec92e543fd5d91f1d3e0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 May 2024 16:53:09 +1200 Subject: [PATCH 02/11] Remove random shared tables assignment in preparation of setting as override instead --- app/controllers/api/projects.php | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index c07c708bd4..f90dda6cab 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -125,33 +125,8 @@ App::post('/v1/projects') throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project."); } - // TODO: 1 in 5 projects use shared tables. Temporary until all projects are using shared tables. - if ( - ( - !\mt_rand(0, 4) - && System::getEnv('_APP_DATABASE_SHARED_TABLES', 'enabled') === 'enabled' - && System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted' - ) || - ( - $dsn === DATABASE_SHARED_TABLES - ) - ) { - $schema = 'appwrite'; - $database = 'appwrite'; - $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database; - - if (!empty($namespace)) { - $dsn .= '&namespace=' . $namespace; - } - } - - // TODO: Allow overriding in development mode. Temporary until all projects are using shared tables. - if ( - App::isDevelopment() - && System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted' - && $request->getHeader('x-appwrited-share-tables', false) - ) { + // TODO: Temporary until all projects are using shared tables. + if ($dsn === DATABASE_SHARED_TABLES || (App::isDevelopment() && $request->getHeader('x-appwrite-shared-tables', false))) { $schema = 'appwrite'; $database = 'appwrite'; $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); From e19b465ce2bd48019dd362f24e3fdaf711d5040e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 May 2024 20:10:47 +1200 Subject: [PATCH 03/11] Move shared tables db to env var from const --- app/cli.php | 4 ++-- app/controllers/api/projects.php | 6 +++--- app/init.php | 7 ++----- app/realtime.php | 2 +- app/worker.php | 6 +++--- src/Appwrite/Platform/Workers/Deletes.php | 6 +++--- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/cli.php b/app/cli.php index da7d23c18d..69d4c1c5a8 100644 --- a/app/cli.php +++ b/app/cli.php @@ -109,7 +109,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -133,7 +133,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index f90dda6cab..26a6431f11 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -126,11 +126,11 @@ App::post('/v1/projects') } // TODO: Temporary until all projects are using shared tables. - if ($dsn === DATABASE_SHARED_TABLES || (App::isDevelopment() && $request->getHeader('x-appwrite-shared-tables', false))) { + if ($dsn === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $schema = 'appwrite'; $database = 'appwrite'; $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database; + $dsn = $schema . '://' . System::getEnv('_APP_DATABASE_SHARED_TABLES', '') . '?database=' . $database; if (!empty($namespace)) { $dsn .= '&namespace=' . $namespace; @@ -184,7 +184,7 @@ App::post('/v1/projects') $adapter = $pools->get($dsn->getHost())->pop()->getResource(); $dbForProject = new Database($adapter, $cache); - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $dbForProject ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/init.php b/app/init.php index 57ad5fab87..c6c9c401a7 100644 --- a/app/init.php +++ b/app/init.php @@ -143,9 +143,6 @@ const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite'; const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1'; const APP_HOSTNAME_INTERNAL = 'appwrite'; -// Databases -const DATABASE_SHARED_TABLES = 'database_db_fra1_self_hosted_16_0'; - // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; const DATABASE_RECONNECT_MAX_ATTEMPTS = 10; @@ -1338,7 +1335,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -1391,7 +1388,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, ->setMetadata('project', $project->getId()) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/realtime.php b/app/realtime.php index 2904b1db9c..cde4327417 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -92,7 +92,7 @@ if (!function_exists("getProjectDB")) { $database = new Database($adapter, getCache()); - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/worker.php b/app/worker.php index 763ab1d914..5d72d52635 100644 --- a/app/worker.php +++ b/app/worker.php @@ -94,7 +94,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -127,7 +127,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -151,7 +151,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 49b41da495..d54f3f5079 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -498,14 +498,14 @@ class Deletes extends Action $collections = $dbForProject->listCollections($limit); foreach ($collections as $collection) { - if ($dsn->getHost() !== DATABASE_SHARED_TABLES || !\in_array($collection->getId(), $projectCollectionIds)) { + if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) { $dbForProject->deleteCollection($collection->getId()); } else { $this->deleteByGroup($collection->getId(), [], database: $dbForProject); } } - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections); if (empty(\array_diff($collectionsIds, $projectCollectionIds))) { @@ -554,7 +554,7 @@ class Deletes extends Action ], $dbForConsole); // Delete metadata table - if ($dsn->getHost() !== DATABASE_SHARED_TABLES) { + if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $dbForProject->deleteCollection('_metadata'); } else { $this->deleteByGroup('_metadata', [], $dbForProject); From 00c047d804aa3d25565552cd9fce941abab2ca76 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 May 2024 20:29:06 +1200 Subject: [PATCH 04/11] Run full tests on shared tables --- .github/workflows/tests.yml | 3 + docker-compose.yml | 40 +- .../Projects/ProjectsConsoleClientTest.php | 501 ------------------ 3 files changed, 21 insertions(+), 523 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2cc4c700f7..7bc39392ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,3 +145,6 @@ jobs: - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug + + - name: Run ${{matrix.service}} Shared Tables Tests + run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug diff --git a/docker-compose.yml b/docker-compose.yml index c104102a76..168b5271f2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -189,6 +189,7 @@ services: - _APP_CONSOLE_COUNTRIES_DENYLIST - _APP_EXPERIMENT_LOGGING_PROVIDER - _APP_EXPERIMENT_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-realtime: entrypoint: realtime @@ -238,6 +239,7 @@ services: - _APP_USAGE_STATS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-worker-audits: entrypoint: worker-audits @@ -267,6 +269,7 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-worker-webhooks: entrypoint: worker-webhooks @@ -299,6 +302,7 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_WEBHOOK_MAX_FAILED_ATTEMPTS + - _APP_DATABASE_SHARED_TABLES appwrite-worker-deletes: entrypoint: worker-deletes @@ -356,6 +360,7 @@ services: - _APP_LOGGING_CONFIG - _APP_EXECUTOR_SECRET - _APP_EXECUTOR_HOST + - _APP_DATABASE_SHARED_TABLES appwrite-worker-databases: entrypoint: worker-databases @@ -387,6 +392,7 @@ services: - _APP_LOGGING_CONFIG - _APP_WORKERS_NUM - _APP_QUEUE_NAME + - _APP_DATABASE_SHARED_TABLES appwrite-worker-builds: entrypoint: worker-builds @@ -452,6 +458,7 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET + - _APP_DATABASE_SHARED_TABLES appwrite-worker-certificates: entrypoint: worker-certificates @@ -487,6 +494,7 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-worker-functions: entrypoint: worker-functions @@ -526,6 +534,7 @@ services: - _APP_DOCKER_HUB_PASSWORD - _APP_LOGGING_CONFIG - _APP_LOGGING_PROVIDER + - _APP_DATABASE_SHARED_TABLES appwrite-worker-mails: entrypoint: worker-mails @@ -560,6 +569,7 @@ services: - _APP_LOGGING_CONFIG - _APP_DOMAIN - _APP_OPTIONS_FORCE_HTTPS + - _APP_DATABASE_SHARED_TABLES appwrite-worker-messaging: entrypoint: worker-messaging @@ -592,6 +602,7 @@ services: - _APP_SMS_FROM - _APP_SMS_PROVIDER - _APP_SMS_PROJECTS_DENY_LIST + - _APP_DATABASE_SHARED_TABLES appwrite-worker-migrations: entrypoint: worker-migrations @@ -627,6 +638,7 @@ services: - _APP_LOGGING_CONFIG - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET + - _APP_DATABASE_SHARED_TABLES appwrite-task-maintenance: entrypoint: maintenance @@ -664,6 +676,7 @@ services: - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_MAINTENANCE_DELAY + - _APP_DATABASE_SHARED_TABLES appwrite-worker-usage: entrypoint: worker-usage @@ -695,6 +708,7 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_USAGE_AGGREGATION_INTERVAL + - _APP_DATABASE_SHARED_TABLES appwrite-worker-usage-dump: entrypoint: worker-usage-dump @@ -726,6 +740,7 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_USAGE_AGGREGATION_INTERVAL + - _APP_DATABASE_SHARED_TABLES appwrite-task-scheduler-functions: entrypoint: schedule-functions @@ -753,6 +768,7 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_DATABASE_SHARED_TABLES appwrite-task-scheduler-messages: entrypoint: schedule-messages @@ -780,6 +796,7 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_DATABASE_SHARED_TABLES appwrite-assistant: container_name: appwrite-assistant @@ -878,20 +895,7 @@ services: - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: "mysqld --innodb-flush-method=fsync" # add ' --query_cache_size=0' for DB tests - # command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu - - # smtp: - # image: appwrite/smtp:1.2.0 - # container_name: appwrite-smtp - # restart: unless-stopped - # networks: - # - appwrite - # environment: - # - LOCAL_DOMAINS=@ - # - RELAY_FROM_HOSTS=192.168.0.0/16 ; *.yourdomain.com - # - SMARTHOST_HOST=smtp - # - SMARTHOST_PORT=587 + command: "mysqld --innodb-flush-method=fsync" redis: image: redis:7.2.4-alpine @@ -909,14 +913,6 @@ services: volumes: - appwrite-redis:/data:rw - # clamav: - # image: appwrite/clamav:1.2.0 - # container_name: appwrite-clamav - # networks: - # - appwrite - # volumes: - # - appwrite-uploads:/storage/uploads - # Dev Tools Start ------------------------------------------------------------------------------------------ # # The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 62f5cfe435..5e0a48642b 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -9,7 +9,6 @@ use Tests\E2E\General\UsageTest; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -3494,504 +3493,4 @@ class ProjectsConsoleClientTest extends Scope return $data; } - - public function testTenantIsolation(): void - { - // Create a team and a project - $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'Amazing Team', - ]); - - $teamId = $team['body']['$id']; - - // Project-level isolation - $project1 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => false - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Application level isolation (shared tables) - $project2 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => true - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Project-level isolation - $project3 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => false - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Application level isolation (shared tables) - $project4 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => true - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Create and API key in each project - $key1 = $this->client->call(Client::METHOD_POST, '/projects/' . $project1['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - $key2 = $this->client->call(Client::METHOD_POST, '/projects/' . $project2['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - $key3 = $this->client->call(Client::METHOD_POST, '/projects/' . $project3['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - $key4 = $this->client->call(Client::METHOD_POST, '/projects/' . $project4['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - // Create a database in each project - $database1 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - $database3 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - $database4 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - // Create a collection in each project - $collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => $database1['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - $collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - $collection3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => $database3['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - $collection4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => $database4['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - // Create an attribute in each project - $attribute1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => $database1['body']['$id'], - 'collectionId' => $collection1['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - $attribute2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => $collection2['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - $attribute3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => $database3['body']['$id'], - 'collectionId' => $collection3['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - $attribute4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => $database4['body']['$id'], - 'collectionId' => $collection4['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - // Wait for attributes - \sleep(2); - - // Create an index in each project - $index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => $database1['body']['$id'], - 'collectionId' => $collection1['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute1['body']['key']], - ]); - - $index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => $collection2['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute2['body']['key']], - ]); - - $index3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => $database3['body']['$id'], - 'collectionId' => $collection3['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute3['body']['key']], - ]); - - $index4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => $database4['body']['$id'], - 'collectionId' => $collection4['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute4['body']['key']], - ]); - - // Wait for indexes - \sleep(2); - - // Assert that each project has only 1 database, 1 collection, 1 attribute and 1 index - $databasesProject1 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject1['body']['total']); - $this->assertEquals(1, \count($databasesProject1['body']['databases'])); - - $databasesProject2 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject2['body']['total']); - $this->assertEquals(1, \count($databasesProject2['body']['databases'])); - - $databasesProject3 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject3['body']['total']); - $this->assertEquals(1, \count($databasesProject3['body']['databases'])); - - $databasesProject4 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject4['body']['total']); - $this->assertEquals(1, \count($databasesProject4['body']['databases'])); - - $collectionsProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject1['body']['total']); - $this->assertEquals(1, \count($collectionsProject1['body']['collections'])); - - $collectionsProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject2['body']['total']); - $this->assertEquals(1, \count($collectionsProject2['body']['collections'])); - - $collectionsProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject3['body']['total']); - $this->assertEquals(1, \count($collectionsProject3['body']['collections'])); - - $collectionsProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject4['body']['total']); - $this->assertEquals(1, \count($collectionsProject4['body']['collections'])); - - $attributesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject1['body']['total']); - $this->assertEquals(1, \count($attributesProject1['body']['attributes'])); - $this->assertEquals('available', $attributesProject1['body']['attributes'][0]['status']); - - $attributesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject2['body']['total']); - $this->assertEquals(1, \count($attributesProject2['body']['attributes'])); - $this->assertEquals('available', $attributesProject2['body']['attributes'][0]['status']); - - $attributesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject3['body']['total']); - $this->assertEquals(1, \count($attributesProject3['body']['attributes'])); - $this->assertEquals('available', $attributesProject3['body']['attributes'][0]['status']); - - $attributesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject4['body']['total']); - $this->assertEquals(1, \count($attributesProject4['body']['attributes'])); - $this->assertEquals('available', $attributesProject4['body']['attributes'][0]['status']); - - $indexesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject1['body']['total']); - $this->assertEquals(1, \count($indexesProject1['body']['indexes'])); - - $indexesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject2['body']['total']); - $this->assertEquals(1, \count($indexesProject2['body']['indexes'])); - - $indexesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject3['body']['total']); - $this->assertEquals(1, \count($indexesProject3['body']['indexes'])); - - $indexesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject4['body']['total']); - $this->assertEquals(1, \count($indexesProject4['body']['indexes'])); - - // Attempt to read cross-type resources - $collectionProject2WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject2WithProject1Key['headers']['status-code']); - - $collectionProject1WithProject2Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject1WithProject2Key['headers']['status-code']); - - // Attempt to read cross-tenant resources - $collectionProject3WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject3WithProject1Key['headers']['status-code']); - - $collectionProject1WithProject3Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject1WithProject3Key['headers']['status-code']); - - // Assert that shared project resources can have the same ID as they're unique on tenant + ID not just ID - $collection5 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => $collection4['body']['$id'], - 'name' => 'Amazing Collection', - ]); - - $this->assertEquals(201, $collection5['headers']['status-code']); - - // Assert that users across projects on shared tables can have the same email as they're unique on tenant + email not just email - $user1 = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'userId' => 'user', - 'email' => 'test@appwrite.io', - 'password' => 'password', - 'name' => 'Test User', - ]); - - $this->assertEquals(201, $user1['headers']['status-code']); - - $user2 = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'userId' => 'user', - 'email' => 'test@appwrite.io', - 'password' => 'password', - 'name' => 'Test User', - ]); - - $this->assertEquals(201, $user2['headers']['status-code']); - } } From bbb4b0bc12d6e0a0441c2417604cd83ba4b16302 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 May 2024 20:29:25 +1200 Subject: [PATCH 05/11] Remove shared table headers --- app/controllers/general.php | 4 ++-- tests/e2e/General/HTTPTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index f1be7bd614..3e243a90d3 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -580,7 +580,7 @@ App::init() ->addHeader('Server', 'Appwrite') ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $refDomain) ->addHeader('Access-Control-Allow-Credentials', 'true'); @@ -631,7 +631,7 @@ App::options() $response ->addHeader('Server', 'Appwrite') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $origin) ->addHeader('Access-Control-Allow-Credentials', 'true') diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index 0bb5ca4650..92bc52561c 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -31,7 +31,7 @@ class HTTPTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEquals('Appwrite', $response['headers']['server']); $this->assertEquals('GET, POST, PUT, PATCH, DELETE', $response['headers']['access-control-allow-methods']); - $this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']); + $this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']); $this->assertEquals('X-Appwrite-Session, X-Fallback-Cookies', $response['headers']['access-control-expose-headers']); $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); $this->assertEquals('true', $response['headers']['access-control-allow-credentials']); From e28224976abec4ec832a13d5aa2d29c2c35ba78c Mon Sep 17 00:00:00 2001 From: shimon Date: Sun, 2 Jun 2024 16:17:11 +0300 Subject: [PATCH 06/11] refactor redis cache --- composer.json | 4 ++-- composer.lock | 58 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 205fe308f0..5ea7a0952b 100644 --- a/composer.json +++ b/composer.json @@ -47,10 +47,10 @@ "utopia-php/abuse": "0.37.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.39.*", - "utopia-php/cache": "0.9.*", + "utopia-php/cache": "dev-refactor-redis-cache as 0.9.1", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.49.*", + "utopia-php/database": "dev-refactor-cache-flow as 0.49.10", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 8acbbed541..dd9d5e263d 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": "53996479cd4ba0c73dbc72d46b240be0", + "content-hash": "42f081ea081021d2f2734fde998ebae5", "packages": [ { "name": "adhocore/jwt", @@ -1569,16 +1569,16 @@ }, { "name": "utopia-php/cache", - "version": "0.9.1", + "version": "dev-refactor-redis-cache", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6" + "reference": "91166f3048af153b48bf7008415fe82785398970" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/552b4c554bb14d0c529631ce304cdf4a2b9d06a6", - "reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/91166f3048af153b48bf7008415fe82785398970", + "reference": "91166f3048af153b48bf7008415fe82785398970", "shasum": "" }, "require": { @@ -1613,9 +1613,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.9.1" + "source": "https://github.com/utopia-php/cache/tree/refactor-redis-cache" }, - "time": "2024-03-19T17:07:20+00:00" + "time": "2024-06-02T10:08:18+00:00" }, { "name": "utopia-php/cli", @@ -1719,16 +1719,16 @@ }, { "name": "utopia-php/database", - "version": "0.49.10", + "version": "dev-refactor-cache-flow", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "216209121bc97a2010f67a39c561fafe1e936bec" + "reference": "13f4729eb2f13fe4b8db2be3c12ceae32489b4e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/216209121bc97a2010f67a39c561fafe1e936bec", - "reference": "216209121bc97a2010f67a39c561fafe1e936bec", + "url": "https://api.github.com/repos/utopia-php/database/zipball/13f4729eb2f13fe4b8db2be3c12ceae32489b4e5", + "reference": "13f4729eb2f13fe4b8db2be3c12ceae32489b4e5", "shasum": "" }, "require": { @@ -1769,9 +1769,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.10" + "source": "https://github.com/utopia-php/database/tree/refactor-cache-flow" }, - "time": "2024-05-20T02:14:20+00:00" + "time": "2024-06-02T11:00:00+00:00" }, { "name": "utopia-php/domains", @@ -3824,16 +3824,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.0", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { @@ -3865,9 +3865,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.29.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2024-05-06T12:04:23+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5587,9 +5587,25 @@ "time": "2023-11-21T18:54:41+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/cache", + "version": "dev-refactor-redis-cache", + "alias": "0.9.1", + "alias_normalized": "0.9.1.0" + }, + { + "package": "utopia-php/database", + "version": "dev-refactor-cache-flow", + "alias": "0.49.10", + "alias_normalized": "0.49.10.0" + } + ], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "utopia-php/cache": 20, + "utopia-php/database": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { From 5b3a3d5bf965d9e76947c9191a909fa1010fbd7b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 4 Jun 2024 20:10:45 +1200 Subject: [PATCH 07/11] Add flaky test retry --- tests/e2e/Services/Functions/FunctionsCustomClientTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 119c1a2223..966f948ce6 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Functions; +use Appwrite\Tests\Retry; use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; @@ -42,6 +43,7 @@ class FunctionsCustomClientTest extends Scope return []; } + #[Retry(count: 2)] public function testCreateExecution(): array { /** From e2c6c6108313d955d519a5abcc25bfe36228351c Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 5 Jun 2024 20:40:14 +0300 Subject: [PATCH 08/11] composer --- composer.json | 4 ++-- composer.lock | 62 +++++++++++++++++++-------------------------------- 2 files changed, 25 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index 5ea7a0952b..c847612f65 100644 --- a/composer.json +++ b/composer.json @@ -47,10 +47,10 @@ "utopia-php/abuse": "0.37.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.39.*", - "utopia-php/cache": "dev-refactor-redis-cache as 0.9.1", + "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-refactor-cache-flow as 0.49.10", + "utopia-php/database": "0.49.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index dd9d5e263d..836ddda152 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": "42f081ea081021d2f2734fde998ebae5", + "content-hash": "44dc3f42d5df8bab8d3e45ff631f384d", "packages": [ { "name": "adhocore/jwt", @@ -1569,16 +1569,16 @@ }, { "name": "utopia-php/cache", - "version": "dev-refactor-redis-cache", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "91166f3048af153b48bf7008415fe82785398970" + "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/91166f3048af153b48bf7008415fe82785398970", - "reference": "91166f3048af153b48bf7008415fe82785398970", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/313bcdfbb166f75c2c205a59d1467cead63a9626", + "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626", "shasum": "" }, "require": { @@ -1613,9 +1613,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/refactor-redis-cache" + "source": "https://github.com/utopia-php/cache/tree/0.10.0" }, - "time": "2024-06-02T10:08:18+00:00" + "time": "2024-06-05T16:40:43+00:00" }, { "name": "utopia-php/cli", @@ -1719,23 +1719,23 @@ }, { "name": "utopia-php/database", - "version": "dev-refactor-cache-flow", + "version": "0.49.12", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "13f4729eb2f13fe4b8db2be3c12ceae32489b4e5" + "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/13f4729eb2f13fe4b8db2be3c12ceae32489b4e5", - "reference": "13f4729eb2f13fe4b8db2be3c12ceae32489b4e5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", + "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/cache": "0.9.*", + "utopia-php/cache": "0.10.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, @@ -1769,9 +1769,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/refactor-cache-flow" + "source": "https://github.com/utopia-php/database/tree/0.49.12" }, - "time": "2024-06-02T11:00:00+00:00" + "time": "2024-06-05T16:52:59+00:00" }, { "name": "utopia-php/domains", @@ -2755,22 +2755,22 @@ }, { "name": "utopia-php/vcs", - "version": "0.6.6", + "version": "0.6.7", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4" + "reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/e538264cfee5e3efdfe1771efba04750cf20b2c4", - "reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974", + "reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", - "utopia-php/cache": "^0.9.0", + "utopia-php/cache": "^0.10.0", "utopia-php/framework": "0.*.*" }, "require-dev": { @@ -2798,9 +2798,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.6.6" + "source": "https://github.com/utopia-php/vcs/tree/0.6.7" }, - "time": "2024-05-17T09:36:30+00:00" + "time": "2024-06-05T17:38:29+00:00" }, { "name": "utopia-php/websocket", @@ -5587,25 +5587,9 @@ "time": "2023-11-21T18:54:41+00:00" } ], - "aliases": [ - { - "package": "utopia-php/cache", - "version": "dev-refactor-redis-cache", - "alias": "0.9.1", - "alias_normalized": "0.9.1.0" - }, - { - "package": "utopia-php/database", - "version": "dev-refactor-cache-flow", - "alias": "0.49.10", - "alias_normalized": "0.49.10.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/cache": 20, - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From f06e19d8fa194ec6f0a67a6ad3ff0a17c614af73 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 5 Jun 2024 20:52:35 +0300 Subject: [PATCH 09/11] composer --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 836ddda152..929765b74a 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": "44dc3f42d5df8bab8d3e45ff631f384d", + "content-hash": "9ba1190efa21ae307532896397b3228d", "packages": [ { "name": "adhocore/jwt", @@ -2119,16 +2119,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.11.0", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "b499c3ad11af711c28252c62d83f24e6106a2154" + "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/b499c3ad11af711c28252c62d83f24e6106a2154", - "reference": "b499c3ad11af711c28252c62d83f24e6106a2154", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/6e466d3511981291843c6ebf9ce3f44fc75e37b0", + "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0", "shasum": "" }, "require": { @@ -2164,9 +2164,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.11.0" + "source": "https://github.com/utopia-php/messaging/tree/0.12.0" }, - "time": "2024-05-08T17:10:02+00:00" + "time": "2024-05-30T14:58:25+00:00" }, { "name": "utopia-php/migration", From ff6efc28557754e3b93a4f8f0435b18ad7ab1aff Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 6 Jun 2024 00:11:56 +0530 Subject: [PATCH 10/11] Revert "Feat remove random shared tables" --- .github/workflows/tests.yml | 3 - app/cli.php | 4 +- app/controllers/api/projects.php | 60 ++- app/controllers/general.php | 4 +- app/init.php | 7 +- app/realtime.php | 2 +- app/worker.php | 6 +- docker-compose.yml | 40 +- src/Appwrite/Platform/Workers/Deletes.php | 6 +- tests/e2e/General/HTTPTest.php | 2 +- .../Functions/FunctionsCustomClientTest.php | 2 - .../Projects/ProjectsConsoleClientTest.php | 501 ++++++++++++++++++ 12 files changed, 596 insertions(+), 41 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7bc39392ef..2cc4c700f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,6 +145,3 @@ jobs: - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug - - - name: Run ${{matrix.service}} Shared Tables Tests - run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug diff --git a/app/cli.php b/app/cli.php index 69d4c1c5a8..da7d23c18d 100644 --- a/app/cli.php +++ b/app/cli.php @@ -109,7 +109,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -133,7 +133,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 91f19a2358..f872ef311b 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -111,8 +111,35 @@ App::post('/v1/projects') $projectId = ($projectId == 'unique()') ? ID::unique() : $projectId; + $backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '05:00']; + $backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '02:00']; + $backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '02:00']; + $backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '02:00']; + $backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '02:00']; + $backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00']; + $databases = Config::getParam('pools-database', []); + /** + * Remove databases from the list that are currently undergoing an backup + */ + if (count($databases) > 1) { + $now = new \DateTime(); + + foreach ($databases as $index => $database) { + if (empty($backups[$database])) { + continue; + } + $backup = $backups[$database]; + $from = \DateTime::createFromFormat('H:i', $backup['from']); + $to = \DateTime::createFromFormat('H:i', $backup['to']); + if ($now >= $from && $now <= $to) { + unset($databases[$index]); + break; + } + } + } + $databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE'); $index = \array_search($databaseOverride, $databases); if ($index !== false) { @@ -125,12 +152,37 @@ App::post('/v1/projects') throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project."); } - // TODO: Temporary until all projects are using shared tables. - if ($dsn === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + // TODO: 1 in 5 projects use shared tables. Temporary until all projects are using shared tables. + if ( + ( + !\mt_rand(0, 4) + && System::getEnv('_APP_DATABASE_SHARED_TABLES', 'enabled') === 'enabled' + && System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted' + ) || + ( + $dsn === DATABASE_SHARED_TABLES + ) + ) { $schema = 'appwrite'; $database = 'appwrite'; $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $dsn = $schema . '://' . System::getEnv('_APP_DATABASE_SHARED_TABLES', '') . '?database=' . $database; + $dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database; + + if (!empty($namespace)) { + $dsn .= '&namespace=' . $namespace; + } + } + + // TODO: Allow overriding in development mode. Temporary until all projects are using shared tables. + if ( + App::isDevelopment() + && System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted' + && $request->getHeader('x-appwrited-share-tables', false) + ) { + $schema = 'appwrite'; + $database = 'appwrite'; + $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); + $dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database; if (!empty($namespace)) { $dsn .= '&namespace=' . $namespace; @@ -184,7 +236,7 @@ App::post('/v1/projects') $adapter = $pools->get($dsn->getHost())->pop()->getResource(); $dbForProject = new Database($adapter, $cache); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $dbForProject ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/controllers/general.php b/app/controllers/general.php index 92ddec58f5..15ba10da36 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -584,7 +584,7 @@ App::init() ->addHeader('Server', 'Appwrite') ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $refDomain) ->addHeader('Access-Control-Allow-Credentials', 'true'); @@ -635,7 +635,7 @@ App::options() $response ->addHeader('Server', 'Appwrite') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $origin) ->addHeader('Access-Control-Allow-Credentials', 'true') diff --git a/app/init.php b/app/init.php index a86156c750..6df2c02908 100644 --- a/app/init.php +++ b/app/init.php @@ -143,6 +143,9 @@ const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite'; const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1'; const APP_HOSTNAME_INTERNAL = 'appwrite'; +// Databases +const DATABASE_SHARED_TABLES = 'database_db_fra1_self_hosted_16_0'; + // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; const DATABASE_RECONNECT_MAX_ATTEMPTS = 10; @@ -1335,7 +1338,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -1388,7 +1391,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, ->setMetadata('project', $project->getId()) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/realtime.php b/app/realtime.php index cde4327417..2904b1db9c 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -92,7 +92,7 @@ if (!function_exists("getProjectDB")) { $database = new Database($adapter, getCache()); - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/worker.php b/app/worker.php index 314fc65b8e..60358ad6b2 100644 --- a/app/worker.php +++ b/app/worker.php @@ -93,7 +93,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -126,7 +126,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -150,7 +150,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/docker-compose.yml b/docker-compose.yml index b86af1c12b..b1e30a1e07 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -189,7 +189,6 @@ services: - _APP_CONSOLE_COUNTRIES_DENYLIST - _APP_EXPERIMENT_LOGGING_PROVIDER - _APP_EXPERIMENT_LOGGING_CONFIG - - _APP_DATABASE_SHARED_TABLES appwrite-realtime: entrypoint: realtime @@ -239,7 +238,6 @@ services: - _APP_USAGE_STATS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - - _APP_DATABASE_SHARED_TABLES appwrite-worker-audits: entrypoint: worker-audits @@ -269,7 +267,6 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - - _APP_DATABASE_SHARED_TABLES appwrite-worker-webhooks: entrypoint: worker-webhooks @@ -302,7 +299,6 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_WEBHOOK_MAX_FAILED_ATTEMPTS - - _APP_DATABASE_SHARED_TABLES appwrite-worker-deletes: entrypoint: worker-deletes @@ -360,7 +356,6 @@ services: - _APP_LOGGING_CONFIG - _APP_EXECUTOR_SECRET - _APP_EXECUTOR_HOST - - _APP_DATABASE_SHARED_TABLES appwrite-worker-databases: entrypoint: worker-databases @@ -392,7 +387,6 @@ services: - _APP_LOGGING_CONFIG - _APP_WORKERS_NUM - _APP_QUEUE_NAME - - _APP_DATABASE_SHARED_TABLES appwrite-worker-builds: entrypoint: worker-builds @@ -458,7 +452,6 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET - - _APP_DATABASE_SHARED_TABLES appwrite-worker-certificates: entrypoint: worker-certificates @@ -494,7 +487,6 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - - _APP_DATABASE_SHARED_TABLES appwrite-worker-functions: entrypoint: worker-functions @@ -534,7 +526,6 @@ services: - _APP_DOCKER_HUB_PASSWORD - _APP_LOGGING_CONFIG - _APP_LOGGING_PROVIDER - - _APP_DATABASE_SHARED_TABLES appwrite-worker-mails: entrypoint: worker-mails @@ -569,7 +560,6 @@ services: - _APP_LOGGING_CONFIG - _APP_DOMAIN - _APP_OPTIONS_FORCE_HTTPS - - _APP_DATABASE_SHARED_TABLES appwrite-worker-messaging: entrypoint: worker-messaging @@ -624,7 +614,6 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET - - _APP_DATABASE_SHARED_TABLES appwrite-worker-migrations: entrypoint: worker-migrations @@ -660,7 +649,6 @@ services: - _APP_LOGGING_CONFIG - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - - _APP_DATABASE_SHARED_TABLES appwrite-task-maintenance: entrypoint: maintenance @@ -698,7 +686,6 @@ services: - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_MAINTENANCE_DELAY - - _APP_DATABASE_SHARED_TABLES appwrite-worker-usage: entrypoint: worker-usage @@ -730,7 +717,6 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_USAGE_AGGREGATION_INTERVAL - - _APP_DATABASE_SHARED_TABLES appwrite-worker-usage-dump: entrypoint: worker-usage-dump @@ -762,7 +748,6 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_USAGE_AGGREGATION_INTERVAL - - _APP_DATABASE_SHARED_TABLES appwrite-task-scheduler-functions: entrypoint: schedule-functions @@ -790,7 +775,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES appwrite-task-scheduler-messages: entrypoint: schedule-messages @@ -818,7 +802,6 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES appwrite-assistant: container_name: appwrite-assistant @@ -917,7 +900,20 @@ services: - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: "mysqld --innodb-flush-method=fsync" + command: "mysqld --innodb-flush-method=fsync" # add ' --query_cache_size=0' for DB tests + # command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu + + # smtp: + # image: appwrite/smtp:1.2.0 + # container_name: appwrite-smtp + # restart: unless-stopped + # networks: + # - appwrite + # environment: + # - LOCAL_DOMAINS=@ + # - RELAY_FROM_HOSTS=192.168.0.0/16 ; *.yourdomain.com + # - SMARTHOST_HOST=smtp + # - SMARTHOST_PORT=587 redis: image: redis:7.2.4-alpine @@ -935,6 +931,14 @@ services: volumes: - appwrite-redis:/data:rw + # clamav: + # image: appwrite/clamav:1.2.0 + # container_name: appwrite-clamav + # networks: + # - appwrite + # volumes: + # - appwrite-uploads:/storage/uploads + # Dev Tools Start ------------------------------------------------------------------------------------------ # # The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index d54f3f5079..49b41da495 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -498,14 +498,14 @@ class Deletes extends Action $collections = $dbForProject->listCollections($limit); foreach ($collections as $collection) { - if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) { + if ($dsn->getHost() !== DATABASE_SHARED_TABLES || !\in_array($collection->getId(), $projectCollectionIds)) { $dbForProject->deleteCollection($collection->getId()); } else { $this->deleteByGroup($collection->getId(), [], database: $dbForProject); } } - if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() === DATABASE_SHARED_TABLES) { $collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections); if (empty(\array_diff($collectionsIds, $projectCollectionIds))) { @@ -554,7 +554,7 @@ class Deletes extends Action ], $dbForConsole); // Delete metadata table - if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + if ($dsn->getHost() !== DATABASE_SHARED_TABLES) { $dbForProject->deleteCollection('_metadata'); } else { $this->deleteByGroup('_metadata', [], $dbForProject); diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index 92bc52561c..0bb5ca4650 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -31,7 +31,7 @@ class HTTPTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEquals('Appwrite', $response['headers']['server']); $this->assertEquals('GET, POST, PUT, PATCH, DELETE', $response['headers']['access-control-allow-methods']); - $this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']); + $this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']); $this->assertEquals('X-Appwrite-Session, X-Fallback-Cookies', $response['headers']['access-control-expose-headers']); $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); $this->assertEquals('true', $response['headers']['access-control-allow-credentials']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 966f948ce6..119c1a2223 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -2,7 +2,6 @@ namespace Tests\E2E\Services\Functions; -use Appwrite\Tests\Retry; use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; @@ -43,7 +42,6 @@ class FunctionsCustomClientTest extends Scope return []; } - #[Retry(count: 2)] public function testCreateExecution(): array { /** diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 2af0c8f1cc..8cdd325501 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -9,6 +9,7 @@ use Tests\E2E\General\UsageTest; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -3493,4 +3494,504 @@ class ProjectsConsoleClientTest extends Scope return $data; } + + public function testTenantIsolation(): void + { + // Create a team and a project + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Amazing Team', + ]); + + $teamId = $team['body']['$id']; + + // Project-level isolation + $project1 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-shared-tables' => false + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Amazing Project', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + // Application level isolation (shared tables) + $project2 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-shared-tables' => true + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Amazing Project', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + // Project-level isolation + $project3 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-shared-tables' => false + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Amazing Project', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + // Application level isolation (shared tables) + $project4 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-shared-tables' => true + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Amazing Project', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + // Create and API key in each project + $key1 = $this->client->call(Client::METHOD_POST, '/projects/' . $project1['body']['$id'] . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], + ]); + + $key2 = $this->client->call(Client::METHOD_POST, '/projects/' . $project2['body']['$id'] . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], + ]); + + $key3 = $this->client->call(Client::METHOD_POST, '/projects/' . $project3['body']['$id'] . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], + ]); + + $key4 = $this->client->call(Client::METHOD_POST, '/projects/' . $project4['body']['$id'] . '/keys', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'name' => 'Key Test', + 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], + ]); + + // Create a database in each project + $database1 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Amazing Database', + ]); + + $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Amazing Database', + ]); + + $database3 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Amazing Database', + ]); + + $database4 = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Amazing Database', + ]); + + // Create a collection in each project + $collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ], [ + 'databaseId' => $database1['body']['$id'], + 'collectionId' => ID::unique(), + 'name' => 'Amazing Collection', + ]); + + $collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ], [ + 'databaseId' => $database2['body']['$id'], + 'collectionId' => ID::unique(), + 'name' => 'Amazing Collection', + ]); + + $collection3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ], [ + 'databaseId' => $database3['body']['$id'], + 'collectionId' => ID::unique(), + 'name' => 'Amazing Collection', + ]); + + $collection4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ], [ + 'databaseId' => $database4['body']['$id'], + 'collectionId' => ID::unique(), + 'name' => 'Amazing Collection', + ]); + + // Create an attribute in each project + $attribute1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ], [ + 'databaseId' => $database1['body']['$id'], + 'collectionId' => $collection1['body']['$id'], + 'key' => ID::unique(), + 'size' => 255, + 'required' => true + ]); + + $attribute2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ], [ + 'databaseId' => $database2['body']['$id'], + 'collectionId' => $collection2['body']['$id'], + 'key' => ID::unique(), + 'size' => 255, + 'required' => true + ]); + + $attribute3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ], [ + 'databaseId' => $database3['body']['$id'], + 'collectionId' => $collection3['body']['$id'], + 'key' => ID::unique(), + 'size' => 255, + 'required' => true + ]); + + $attribute4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ], [ + 'databaseId' => $database4['body']['$id'], + 'collectionId' => $collection4['body']['$id'], + 'key' => ID::unique(), + 'size' => 255, + 'required' => true + ]); + + // Wait for attributes + \sleep(2); + + // Create an index in each project + $index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ], [ + 'databaseId' => $database1['body']['$id'], + 'collectionId' => $collection1['body']['$id'], + 'key' => ID::unique(), + 'type' => Database::INDEX_KEY, + 'attributes' => [$attribute1['body']['key']], + ]); + + $index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ], [ + 'databaseId' => $database2['body']['$id'], + 'collectionId' => $collection2['body']['$id'], + 'key' => ID::unique(), + 'type' => Database::INDEX_KEY, + 'attributes' => [$attribute2['body']['key']], + ]); + + $index3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ], [ + 'databaseId' => $database3['body']['$id'], + 'collectionId' => $collection3['body']['$id'], + 'key' => ID::unique(), + 'type' => Database::INDEX_KEY, + 'attributes' => [$attribute3['body']['key']], + ]); + + $index4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ], [ + 'databaseId' => $database4['body']['$id'], + 'collectionId' => $collection4['body']['$id'], + 'key' => ID::unique(), + 'type' => Database::INDEX_KEY, + 'attributes' => [$attribute4['body']['key']], + ]); + + // Wait for indexes + \sleep(2); + + // Assert that each project has only 1 database, 1 collection, 1 attribute and 1 index + $databasesProject1 = $this->client->call(Client::METHOD_GET, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ]); + + $this->assertEquals(1, $databasesProject1['body']['total']); + $this->assertEquals(1, \count($databasesProject1['body']['databases'])); + + $databasesProject2 = $this->client->call(Client::METHOD_GET, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ]); + + $this->assertEquals(1, $databasesProject2['body']['total']); + $this->assertEquals(1, \count($databasesProject2['body']['databases'])); + + $databasesProject3 = $this->client->call(Client::METHOD_GET, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ]); + + $this->assertEquals(1, $databasesProject3['body']['total']); + $this->assertEquals(1, \count($databasesProject3['body']['databases'])); + + $databasesProject4 = $this->client->call(Client::METHOD_GET, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ]); + + $this->assertEquals(1, $databasesProject4['body']['total']); + $this->assertEquals(1, \count($databasesProject4['body']['databases'])); + + $collectionsProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ]); + + $this->assertEquals(1, $collectionsProject1['body']['total']); + $this->assertEquals(1, \count($collectionsProject1['body']['collections'])); + + $collectionsProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ]); + + $this->assertEquals(1, $collectionsProject2['body']['total']); + $this->assertEquals(1, \count($collectionsProject2['body']['collections'])); + + $collectionsProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ]); + + $this->assertEquals(1, $collectionsProject3['body']['total']); + $this->assertEquals(1, \count($collectionsProject3['body']['collections'])); + + $collectionsProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ]); + + $this->assertEquals(1, $collectionsProject4['body']['total']); + $this->assertEquals(1, \count($collectionsProject4['body']['collections'])); + + $attributesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ]); + + $this->assertEquals(1, $attributesProject1['body']['total']); + $this->assertEquals(1, \count($attributesProject1['body']['attributes'])); + $this->assertEquals('available', $attributesProject1['body']['attributes'][0]['status']); + + $attributesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ]); + + $this->assertEquals(1, $attributesProject2['body']['total']); + $this->assertEquals(1, \count($attributesProject2['body']['attributes'])); + $this->assertEquals('available', $attributesProject2['body']['attributes'][0]['status']); + + $attributesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ]); + + $this->assertEquals(1, $attributesProject3['body']['total']); + $this->assertEquals(1, \count($attributesProject3['body']['attributes'])); + $this->assertEquals('available', $attributesProject3['body']['attributes'][0]['status']); + + $attributesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ]); + + $this->assertEquals(1, $attributesProject4['body']['total']); + $this->assertEquals(1, \count($attributesProject4['body']['attributes'])); + $this->assertEquals('available', $attributesProject4['body']['attributes'][0]['status']); + + $indexesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ]); + + $this->assertEquals(1, $indexesProject1['body']['total']); + $this->assertEquals(1, \count($indexesProject1['body']['indexes'])); + + $indexesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ]); + + $this->assertEquals(1, $indexesProject2['body']['total']); + $this->assertEquals(1, \count($indexesProject2['body']['indexes'])); + + $indexesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ]); + + $this->assertEquals(1, $indexesProject3['body']['total']); + $this->assertEquals(1, \count($indexesProject3['body']['indexes'])); + + $indexesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ]); + + $this->assertEquals(1, $indexesProject4['body']['total']); + $this->assertEquals(1, \count($indexesProject4['body']['indexes'])); + + // Attempt to read cross-type resources + $collectionProject2WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ]); + + $this->assertEquals(404, $collectionProject2WithProject1Key['headers']['status-code']); + + $collectionProject1WithProject2Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ]); + + $this->assertEquals(404, $collectionProject1WithProject2Key['headers']['status-code']); + + // Attempt to read cross-tenant resources + $collectionProject3WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project1['body']['$id'], + 'x-appwrite-key' => $key1['body']['secret'] + ]); + + $this->assertEquals(404, $collectionProject3WithProject1Key['headers']['status-code']); + + $collectionProject1WithProject3Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project3['body']['$id'], + 'x-appwrite-key' => $key3['body']['secret'] + ]); + + $this->assertEquals(404, $collectionProject1WithProject3Key['headers']['status-code']); + + // Assert that shared project resources can have the same ID as they're unique on tenant + ID not just ID + $collection5 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ], [ + 'databaseId' => $database2['body']['$id'], + 'collectionId' => $collection4['body']['$id'], + 'name' => 'Amazing Collection', + ]); + + $this->assertEquals(201, $collection5['headers']['status-code']); + + // Assert that users across projects on shared tables can have the same email as they're unique on tenant + email not just email + $user1 = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project2['body']['$id'], + 'x-appwrite-key' => $key2['body']['secret'] + ], [ + 'userId' => 'user', + 'email' => 'test@appwrite.io', + 'password' => 'password', + 'name' => 'Test User', + ]); + + $this->assertEquals(201, $user1['headers']['status-code']); + + $user2 = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $project4['body']['$id'], + 'x-appwrite-key' => $key4['body']['secret'] + ], [ + 'userId' => 'user', + 'email' => 'test@appwrite.io', + 'password' => 'password', + 'name' => 'Test User', + ]); + + $this->assertEquals(201, $user2['headers']['status-code']); + } } From cdf7e1a8dc73020c792510533fecffb3c48299b1 Mon Sep 17 00:00:00 2001 From: shimon Date: Wed, 5 Jun 2024 22:29:47 +0300 Subject: [PATCH 11/11] composer --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 929765b74a..5ee1aff322 100644 --- a/composer.lock +++ b/composer.lock @@ -1427,16 +1427,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.37.0", + "version": "0.37.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "2de5c12886cbd516e511e559afdd9e615d871062" + "reference": "4dfcff4754c7804d1a70039792c0f2d59a5cc981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/2de5c12886cbd516e511e559afdd9e615d871062", - "reference": "2de5c12886cbd516e511e559afdd9e615d871062", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/4dfcff4754c7804d1a70039792c0f2d59a5cc981", + "reference": "4dfcff4754c7804d1a70039792c0f2d59a5cc981", "shasum": "" }, "require": { @@ -1470,9 +1470,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.37.0" + "source": "https://github.com/utopia-php/abuse/tree/0.37.1" }, - "time": "2024-03-06T21:20:27+00:00" + "time": "2024-06-05T18:03:59+00:00" }, { "name": "utopia-php/analytics", @@ -1522,16 +1522,16 @@ }, { "name": "utopia-php/audit", - "version": "0.39.0", + "version": "0.39.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "f0bc15012e05cc0b9dde012ab27d25f193768a2c" + "reference": "7ea91e0ceea7b94293612fea94022b73315677c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/f0bc15012e05cc0b9dde012ab27d25f193768a2c", - "reference": "f0bc15012e05cc0b9dde012ab27d25f193768a2c", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/7ea91e0ceea7b94293612fea94022b73315677c2", + "reference": "7ea91e0ceea7b94293612fea94022b73315677c2", "shasum": "" }, "require": { @@ -1563,9 +1563,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.39.0" + "source": "https://github.com/utopia-php/audit/tree/0.39.1" }, - "time": "2024-03-06T21:20:37+00:00" + "time": "2024-06-05T19:28:22+00:00" }, { "name": "utopia-php/cache",