diff --git a/.env b/.env index 3dc7afe34a..4a6a3ac344 100644 --- a/.env +++ b/.env @@ -47,6 +47,8 @@ _APP_DB_SCHEMA=appwrite _APP_DB_USER=user _APP_DB_PASS=password _APP_DB_ROOT_PASS=rootsecretpassword +_APP_DATABASE_SHARED_TABLES= +_APP_DATABASE_SHARED_NAMESPACE= _APP_DB_ADAPTER_DOCUMENTSDB=mongodb _APP_DB_HOST_DOCUMENTSDB=mongodb _APP_DB_PORT_DOCUMENTSDB=27017 diff --git a/app/cli.php b/app/cli.php index a6267fa341..ada155c4dc 100644 --- a/app/cli.php +++ b/app/cli.php @@ -157,12 +157,19 @@ $container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, } if (isset($databases[$dsn->getHost()])) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database = $databases[$dsn->getHost()]; $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { @@ -182,9 +189,16 @@ $container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) ->setTenant($project->getSequence()) + ->setGlobalCollections($projectsGlobalCollections) ->setNamespace($dsn->getParam('namespace')); } else { $database @@ -212,6 +226,11 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization return $database; } + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + $adapter = new DatabasePool($pools->get('logs')); $database = new Database($adapter, $cache); @@ -220,6 +239,7 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization ->setAuthorization($authorization) ->setSharedTables(true) ->setNamespace('logsV1') + ->setGlobalCollections($logsCollections) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK) ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); diff --git a/app/init/resources.php b/app/init/resources.php index 29506bfc9c..96457294de 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -159,10 +159,16 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $adapter = new DatabasePool($pools->get('logs')); $database = new Database($adapter, $cache); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + $database ->setDatabase(APP_DATABASE) ->setAuthorization($authorization) ->setSharedTables(true) + ->setGlobalCollections($logsCollections) ->setNamespace('logsV1') ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 1aa53b7403..70d691370d 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -204,9 +204,16 @@ return function (Container $container): void { $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) ->setTenant($project->getSequence()) + ->setGlobalCollections($projectsGlobalCollections) ->setNamespace($dsn->getParam('namespace')); } else { $database @@ -223,6 +230,11 @@ return function (Container $container): void { $adapter = null; return function (?Document $project = null) use ($pools, $cache, $authorization, &$adapter) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + $adapter ??= new DatabasePool($pools->get('logs')); $database = new Database($adapter, $cache); @@ -230,6 +242,7 @@ return function (Container $container): void { ->setDatabase(APP_DATABASE) ->setAuthorization($authorization) ->setSharedTables(true) + ->setGlobalCollections($logsCollections) ->setNamespace('logsV1') ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API) ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); @@ -690,8 +703,15 @@ return function (Container $container): void { $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { @@ -1292,6 +1312,12 @@ return function (Container $container): void { $database = new Database($adapter, $cache); $sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''))); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setDatabase(APP_DATABASE) ->setAuthorization($authorization) @@ -1314,6 +1340,7 @@ return function (Container $container): void { if (\in_array($databaseHost, $dbTypeSharedTables)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($databaseDSN->getParam('namespace')); } else { @@ -1325,6 +1352,7 @@ return function (Container $container): void { } elseif (\in_array($dsn->getHost(), $sharedTables)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { diff --git a/app/init/worker/message.php b/app/init/worker/message.php index dfe6af9bd9..17796fadcd 100644 --- a/app/init/worker/message.php +++ b/app/init/worker/message.php @@ -14,6 +14,7 @@ use Appwrite\Utopia\Database\Documents\User; use Utopia\Audit\Adapter\Database as AdapterDatabase; use Utopia\Audit\Audit as UtopiaAudit; use Utopia\Cache\Cache; +use Utopia\Config\Config; use Utopia\Console; use Utopia\Database\Adapter\Pool as DatabasePool; use Utopia\Database\Database; @@ -90,8 +91,15 @@ return function (Container $container): void { $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { @@ -130,8 +138,15 @@ return function (Container $container): void { $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { @@ -152,8 +167,15 @@ return function (Container $container): void { $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { @@ -210,6 +232,14 @@ return function (Container $container): void { $sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''))); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + + $database->setGlobalCollections($projectsGlobalCollections); + // For separate pools (documentsdb/vectorsdb), check their own shared tables config. // If not configured, use dedicated mode to avoid cross-engine tenant type mismatches. if ($databaseHost !== $dsn->getHost()) { @@ -222,6 +252,7 @@ return function (Container $container): void { if (\in_array($databaseHost, $dbTypeSharedTables)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($projectDocument->getSequence()) ->setNamespace($databaseDSN->getParam('namespace')); } else { @@ -233,6 +264,7 @@ return function (Container $container): void { } elseif (\in_array($dsn->getHost(), $sharedTables, true)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($projectDocument->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { @@ -257,6 +289,11 @@ return function (Container $container): void { return $database; } + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + $adapter = new DatabasePool($pools->get('logs')); $database = new Database($adapter, $cache); @@ -264,6 +301,7 @@ return function (Container $container): void { ->setDatabase(APP_DATABASE) ->setAuthorization($authorization) ->setSharedTables(true) + ->setGlobalCollections($logsCollections) ->setNamespace('logsV1') ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER) ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER); diff --git a/app/realtime.php b/app/realtime.php index 352903d942..826d751b14 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -130,8 +130,14 @@ if (!function_exists('getProjectDB')) { $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); if (\in_array($dsn->getHost(), $sharedTables)) { + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($dsn->getParam('namespace')); } else { diff --git a/composer.lock b/composer.lock index 50b6355c46..3edbc39614 100644 --- a/composer.lock +++ b/composer.lock @@ -3351,16 +3351,16 @@ }, { "name": "utopia-php/abuse", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "20bee84fd14dbe81d50ecabf1ffd81cceca06152" + "reference": "53f4274939353522ba331f55bcff6e6011ffc56c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/20bee84fd14dbe81d50ecabf1ffd81cceca06152", - "reference": "20bee84fd14dbe81d50ecabf1ffd81cceca06152", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/53f4274939353522ba331f55bcff6e6011ffc56c", + "reference": "53f4274939353522ba331f55bcff6e6011ffc56c", "shasum": "" }, "require": { @@ -3397,9 +3397,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/1.2.2" + "source": "https://github.com/utopia-php/abuse/tree/1.2.3" }, - "time": "2026-02-02T10:43:10+00:00" + "time": "2026-04-29T11:19:08+00:00" }, { "name": "utopia-php/agents", @@ -3850,16 +3850,16 @@ }, { "name": "utopia-php/database", - "version": "5.3.22", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "d765945da6b3141852014b2f96ecf1fe7e3d6ba7" + "reference": "688d9422b5ff42ac2ecc29397d94891cfd772e93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/d765945da6b3141852014b2f96ecf1fe7e3d6ba7", - "reference": "d765945da6b3141852014b2f96ecf1fe7e3d6ba7", + "url": "https://api.github.com/repos/utopia-php/database/zipball/688d9422b5ff42ac2ecc29397d94891cfd772e93", + "reference": "688d9422b5ff42ac2ecc29397d94891cfd772e93", "shasum": "" }, "require": { @@ -3903,9 +3903,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/5.3.22" + "source": "https://github.com/utopia-php/database/tree/5.4.1" }, - "time": "2026-04-20T07:12:46+00:00" + "time": "2026-04-29T07:32:59+00:00" }, { "name": "utopia-php/detector", @@ -4062,16 +4062,16 @@ }, { "name": "utopia-php/domains", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "0edf6bb2b07f30db849a267027077bf5abb994c6" + "reference": "c87ba0a1da4cbf75d2cff9d3ea0262b78f1d86f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/0edf6bb2b07f30db849a267027077bf5abb994c6", - "reference": "0edf6bb2b07f30db849a267027077bf5abb994c6", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/c87ba0a1da4cbf75d2cff9d3ea0262b78f1d86f6", + "reference": "c87ba0a1da4cbf75d2cff9d3ea0262b78f1d86f6", "shasum": "" }, "require": { @@ -4118,9 +4118,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/1.0.5" + "source": "https://github.com/utopia-php/domains/tree/1.0.6" }, - "time": "2026-03-03T09:20:50+00:00" + "time": "2026-04-29T11:08:10+00:00" }, { "name": "utopia-php/dsn", @@ -4530,16 +4530,16 @@ }, { "name": "utopia-php/migration", - "version": "1.9.4", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "969dc9477ea962f16da9254facdbd8944cf13477" + "reference": "952a4dfe232702f80e45c35129466a8d8cb4c599" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/969dc9477ea962f16da9254facdbd8944cf13477", - "reference": "969dc9477ea962f16da9254facdbd8944cf13477", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/952a4dfe232702f80e45c35129466a8d8cb4c599", + "reference": "952a4dfe232702f80e45c35129466a8d8cb4c599", "shasum": "" }, "require": { @@ -4579,9 +4579,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.9.4" + "source": "https://github.com/utopia-php/migration/tree/1.9.5" }, - "time": "2026-04-27T12:42:51+00:00" + "time": "2026-04-29T11:19:13+00:00" }, { "name": "utopia-php/mongo", @@ -5020,16 +5020,16 @@ }, { "name": "utopia-php/storage", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "52d1f89a47165ef0d3deff63043cda182175adfb" + "reference": "8a2e3a86fd01aaed675884146665308c2122264e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/52d1f89a47165ef0d3deff63043cda182175adfb", - "reference": "52d1f89a47165ef0d3deff63043cda182175adfb", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/8a2e3a86fd01aaed675884146665308c2122264e", + "reference": "8a2e3a86fd01aaed675884146665308c2122264e", "shasum": "" }, "require": { @@ -5066,9 +5066,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/2.0.0" + "source": "https://github.com/utopia-php/storage/tree/2.0.1" }, - "time": "2026-04-27T11:39:32+00:00" + "time": "2026-04-29T09:05:48+00:00" }, { "name": "utopia-php/system", @@ -6221,11 +6221,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.52", + "version": "2.1.54", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/08a34f8db7ca4daabff74a474fe13c0e56e2b4e5", - "reference": "08a34f8db7ca4daabff74a474fe13c0e56e2b4e5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8be50c3992107dc837b17da4d140fbbdf9a5c5bd", + "reference": "8be50c3992107dc837b17da4d140fbbdf9a5c5bd", "shasum": "" }, "require": { @@ -6270,7 +6270,7 @@ "type": "github" } ], - "time": "2026-04-28T12:17:53+00:00" + "time": "2026-04-29T13:31:09+00:00" }, { "name": "phpunit/php-code-coverage", @@ -8444,7 +8444,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -8465,5 +8465,5 @@ "platform-dev": { "ext-fileinfo": "*" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" }