From 7a9a2899ff32c785ffaa9583037128d28c696955 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 29 Apr 2026 15:41:56 +0300 Subject: [PATCH 1/9] setGlobalCollections --- app/cli.php | 24 ++++++++++++++++++- app/init/resources.php | 10 +++++++- app/init/resources/request.php | 35 ++++++++++++++++++++++++++- app/init/worker/message.php | 44 +++++++++++++++++++++++++++++++++- app/realtime.php | 6 +++++ 5 files changed, 115 insertions(+), 4 deletions(-) diff --git a/app/cli.php b/app/cli.php index a6267fa341..dd7c172cab 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 @@ -225,7 +239,15 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization // set tenant if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - $database->setTenant($project->getSequence()); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + + $database + ->setTenant($project->getSequence()) + ->setGlobalCollections($logsCollections) + ; } return $database; diff --git a/app/init/resources.php b/app/init/resources.php index 29506bfc9c..12438c3fae 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -169,7 +169,15 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization // set tenant if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - $database->setTenant($project->getSequence()); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + + $database + ->setTenant($project->getSequence()) + ->setGlobalCollections($logsCollections) + ; } return $database; diff --git a/app/init/resources/request.php b/app/init/resources/request.php index 1aa53b7403..ec2b24566f 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 @@ -235,7 +242,15 @@ return function (Container $container): void { ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES); if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - $database->setTenant($project->getSequence()); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + + $database + ->setTenant($project->getSequence()) + ->setGlobalCollections($logsCollections) + ; } return $database; @@ -690,8 +705,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 { @@ -1338,6 +1360,17 @@ return function (Container $container): void { $database->setTimeout($timeout); } + if ($database->getSharedTables() && $database->getTenant() !== null) { + //Do we need to set it for DOCUMENTSDB/VECTORSDB??? + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + + $database->setGlobalCollections($projectsGlobalCollections); + } + // Register database event listeners for usage stats collection $documentsMetric = METRIC_DOCUMENTS; $databaseIdDocumentsMetric = METRIC_DATABASE_ID_DOCUMENTS; diff --git a/app/init/worker/message.php b/app/init/worker/message.php index dfe6af9bd9..a9c356bcec 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 { @@ -243,6 +265,18 @@ return function (Container $container): void { } $database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER); + + if ($database->getSharedTables() && $database->getTenant() !== null){ + // Do we need to set for DOCUMENTSDB/VECTORSDB??? + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + + $database->setGlobalCollections($projectsGlobalCollections); + } + return $database; }; }, ['cache', 'register', 'project', 'authorization']); @@ -269,7 +303,15 @@ return function (Container $container): void { ->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER); if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - $database->setTenant($project->getSequence()); + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $logsCollections = $collections['logs'] ?? []; + $logsCollections = array_keys($logsCollections); + + $database + ->setTenant($project->getSequence()) + ->setGlobalCollections($logsCollections) + ; } return $database; 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 { From 8eed06678b857cfbacc41a07728f94034e5d277c Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 29 Apr 2026 15:54:05 +0300 Subject: [PATCH 2/9] formatting --- app/init/resources/request.php | 20 +++++++++----------- app/init/worker/message.php | 22 +++++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/app/init/resources/request.php b/app/init/resources/request.php index ec2b24566f..f118fde7ee 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -1314,6 +1314,13 @@ return function (Container $container): void { $database = new Database($adapter, $cache); $sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''))); + //Do we need to set it for DOCUMENTSDB/VECTORSDB??? + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $projectCollections = $collections['projects'] ?? []; + $projectsGlobalCollections = array_keys($projectCollections); + $projectsGlobalCollections[] = 'audit'; + $database ->setDatabase(APP_DATABASE) ->setAuthorization($authorization) @@ -1336,6 +1343,7 @@ return function (Container $container): void { if (\in_array($databaseHost, $dbTypeSharedTables)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($project->getSequence()) ->setNamespace($databaseDSN->getParam('namespace')); } else { @@ -1347,6 +1355,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 { @@ -1360,17 +1369,6 @@ return function (Container $container): void { $database->setTimeout($timeout); } - if ($database->getSharedTables() && $database->getTenant() !== null) { - //Do we need to set it for DOCUMENTSDB/VECTORSDB??? - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $projectCollections = $collections['projects'] ?? []; - $projectsGlobalCollections = array_keys($projectCollections); - $projectsGlobalCollections[] = 'audit'; - - $database->setGlobalCollections($projectsGlobalCollections); - } - // Register database event listeners for usage stats collection $documentsMetric = METRIC_DOCUMENTS; $databaseIdDocumentsMetric = METRIC_DATABASE_ID_DOCUMENTS; diff --git a/app/init/worker/message.php b/app/init/worker/message.php index a9c356bcec..555784437a 100644 --- a/app/init/worker/message.php +++ b/app/init/worker/message.php @@ -232,6 +232,15 @@ return function (Container $container): void { $sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''))); + // Do we need to set for DOCUMENTSDB/VECTORSDB???????? + /** @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()) { @@ -244,6 +253,7 @@ return function (Container $container): void { if (\in_array($databaseHost, $dbTypeSharedTables)) { $database ->setSharedTables(true) + ->setGlobalCollections($projectsGlobalCollections) ->setTenant($projectDocument->getSequence()) ->setNamespace($databaseDSN->getParam('namespace')); } else { @@ -255,6 +265,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 { @@ -266,17 +277,6 @@ return function (Container $container): void { $database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER); - if ($database->getSharedTables() && $database->getTenant() !== null){ - // Do we need to set for DOCUMENTSDB/VECTORSDB??? - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $projectCollections = $collections['projects'] ?? []; - $projectsGlobalCollections = array_keys($projectCollections); - $projectsGlobalCollections[] = 'audit'; - - $database->setGlobalCollections($projectsGlobalCollections); - } - return $database; }; }, ['cache', 'register', 'project', 'authorization']); From 18b976967258ffad4c971ad2e2010006d9bba17d Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 29 Apr 2026 16:14:21 +0300 Subject: [PATCH 3/9] lock file --- composer.lock | 60 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/composer.lock b/composer.lock index 2cf57b95a3..c19bd1dfec 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", @@ -5466,16 +5466,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.24.0", + "version": "1.25.1", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "6d4d26659bc7a1c347c1d4d8dae3b77b5562e0cb" + "reference": "f21a556b9acdbf75bbdcdc90a078af641646eade" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6d4d26659bc7a1c347c1d4d8dae3b77b5562e0cb", - "reference": "6d4d26659bc7a1c347c1d4d8dae3b77b5562e0cb", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f21a556b9acdbf75bbdcdc90a078af641646eade", + "reference": "f21a556b9acdbf75bbdcdc90a078af641646eade", "shasum": "" }, "require": { @@ -5511,9 +5511,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.24.0" + "source": "https://github.com/appwrite/sdk-generator/tree/1.25.1" }, - "time": "2026-04-24T12:50:05+00:00" + "time": "2026-04-28T11:12:22+00:00" }, { "name": "brianium/paratest", @@ -6222,11 +6222,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.51", + "version": "2.1.53", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc3b523c45e714c70de2ac5113b958223b55dc59", - "reference": "dc3b523c45e714c70de2ac5113b958223b55dc59", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ef67586798c003274797b288a68b221e4270dca7", + "reference": "ef67586798c003274797b288a68b221e4270dca7", "shasum": "" }, "require": { @@ -6271,7 +6271,7 @@ "type": "github" } ], - "time": "2026-04-21T18:22:01+00:00" + "time": "2026-04-28T16:09:00+00:00" }, { "name": "phpunit/php-code-coverage", @@ -8445,7 +8445,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -8466,5 +8466,5 @@ "platform-dev": { "ext-fileinfo": "*" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } From 9d3255f5cd0ceb8e654e7abec95e7c7bb9aed563 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 29 Apr 2026 17:08:14 +0300 Subject: [PATCH 4/9] Update lock --- composer.lock | 72 +++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) 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" } From d099167d18dab7cad8fc646fcb154d709df94d3a Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 29 Apr 2026 17:09:25 +0300 Subject: [PATCH 5/9] Shared env --- .env | 2 ++ 1 file changed, 2 insertions(+) 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 From 337d47b1d9f5609aa6721f6903c57c4fec9fd4f6 Mon Sep 17 00:00:00 2001 From: frubio Date: Thu, 30 Apr 2026 09:33:08 +0200 Subject: [PATCH 6/9] fix(locale): add Spanish session alert translations --- app/config/locale/translations/es.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/config/locale/translations/es.json b/app/config/locale/translations/es.json index 21a406b418..1bbc8062be 100644 --- a/app/config/locale/translations/es.json +++ b/app/config/locale/translations/es.json @@ -28,6 +28,16 @@ "emails.invitation.thanks": "Gracias.,", "emails.invitation.buttonText": "Aceptar invitación a {{team}}", "emails.invitation.signature": "El equipo de {{project}}", + "emails.sessionAlert.subject": "Alerta de seguridad: nueva sesión en tu cuenta de {{project}}", + "emails.sessionAlert.preview": "Nuevo inicio de sesión detectado en {{project}} a las {{time}} UTC.", + "emails.sessionAlert.hello": "Hola {{user}},", + "emails.sessionAlert.body": "Se ha creado una nueva sesión en tu cuenta de {{b}}{{project}}{{/b}}, {{b}}el {{date}} de {{year}} a las {{time}} UTC{{/b}}.\nEstos son los detalles de la nueva sesión:", + "emails.sessionAlert.listDevice": "Dispositivo: {{b}}{{device}}{{/b}}", + "emails.sessionAlert.listIpAddress": "Dirección IP: {{b}}{{ipAddress}}{{/b}}", + "emails.sessionAlert.listCountry": "País: {{b}}{{country}}{{/b}}", + "emails.sessionAlert.footer": "Si has sido tú, no tienes que hacer nada más.\nSi no has iniciado esta sesión o sospechas actividad no autorizada, protege tu cuenta.", + "emails.sessionAlert.thanks": "Gracias,", + "emails.sessionAlert.signature": "El equipo de {{project}}", "locale.country.unknown": "Desconocido", "countries.af": "Afganistán", "countries.ao": "Angola", From c0bba74eee628b3811a29f8e984ce377b2d3d481 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 30 Apr 2026 10:36:12 +0300 Subject: [PATCH 7/9] set setGlobalCollections logs --- app/cli.php | 16 +++++++--------- app/init/resources.php | 16 +++++++--------- app/init/resources/request.php | 17 +++++++---------- app/init/worker/message.php | 17 +++++++---------- 4 files changed, 28 insertions(+), 38 deletions(-) diff --git a/app/cli.php b/app/cli.php index dd7c172cab..ada155c4dc 100644 --- a/app/cli.php +++ b/app/cli.php @@ -226,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); @@ -234,20 +239,13 @@ $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); // set tenant if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $logsCollections = $collections['logs'] ?? []; - $logsCollections = array_keys($logsCollections); - - $database - ->setTenant($project->getSequence()) - ->setGlobalCollections($logsCollections) - ; + $database->setTenant($project->getSequence()); } return $database; diff --git a/app/init/resources.php b/app/init/resources.php index 12438c3fae..96457294de 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -159,25 +159,23 @@ $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); // set tenant if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $logsCollections = $collections['logs'] ?? []; - $logsCollections = array_keys($logsCollections); - - $database - ->setTenant($project->getSequence()) - ->setGlobalCollections($logsCollections) - ; + $database->setTenant($project->getSequence()); } return $database; diff --git a/app/init/resources/request.php b/app/init/resources/request.php index f118fde7ee..70d691370d 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -230,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); @@ -237,20 +242,13 @@ 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); if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $logsCollections = $collections['logs'] ?? []; - $logsCollections = array_keys($logsCollections); - - $database - ->setTenant($project->getSequence()) - ->setGlobalCollections($logsCollections) - ; + $database->setTenant($project->getSequence()); } return $database; @@ -1314,7 +1312,6 @@ return function (Container $container): void { $database = new Database($adapter, $cache); $sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''))); - //Do we need to set it for DOCUMENTSDB/VECTORSDB??? /** @var array $collections */ $collections = Config::getParam('collections', []); $projectCollections = $collections['projects'] ?? []; diff --git a/app/init/worker/message.php b/app/init/worker/message.php index 555784437a..08900c9bee 100644 --- a/app/init/worker/message.php +++ b/app/init/worker/message.php @@ -232,7 +232,6 @@ return function (Container $container): void { $sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''))); - // Do we need to set for DOCUMENTSDB/VECTORSDB???????? /** @var array $collections */ $collections = Config::getParam('collections', []); $projectCollections = $collections['projects'] ?? []; @@ -291,6 +290,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); @@ -298,20 +302,13 @@ 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); if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') { - /** @var array $collections */ - $collections = Config::getParam('collections', []); - $logsCollections = $collections['logs'] ?? []; - $logsCollections = array_keys($logsCollections); - - $database - ->setTenant($project->getSequence()) - ->setGlobalCollections($logsCollections) - ; + $database->setTenant($project->getSequence()); } return $database; From d98bd8c9723e969a686f372d638ab4e3adfa8964 Mon Sep 17 00:00:00 2001 From: fogelito Date: Thu, 30 Apr 2026 10:44:21 +0300 Subject: [PATCH 8/9] Remove line --- app/init/worker/message.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/init/worker/message.php b/app/init/worker/message.php index 08900c9bee..17796fadcd 100644 --- a/app/init/worker/message.php +++ b/app/init/worker/message.php @@ -275,7 +275,6 @@ return function (Container $container): void { } $database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER); - return $database; }; }, ['cache', 'register', 'project', 'authorization']); From b73ba68bfb13c6fdae4714f3363796ab41046469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 30 Apr 2026 10:21:38 +0200 Subject: [PATCH 9/9] Fix oauth order; Fix apple secreting too much --- .../Console/Http/OAuth2Providers/XList.php | 6 ++-- .../Http/Project/OAuth2/Apple/Update.php | 5 ++-- .../Console/ConsoleConsoleClientTest.php | 2 ++ tests/e2e/Services/Project/OAuth2Base.php | 30 ++++++++----------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Console/Http/OAuth2Providers/XList.php b/src/Appwrite/Platform/Modules/Console/Http/OAuth2Providers/XList.php index 79a36643a1..e253292ca9 100644 --- a/src/Appwrite/Platform/Modules/Console/Http/OAuth2Providers/XList.php +++ b/src/Appwrite/Platform/Modules/Console/Http/OAuth2Providers/XList.php @@ -54,9 +54,9 @@ class XList extends Action $actions = OAuth2Base::getProviderActions(); $providers = []; - foreach ($actions as $providerId => $updateClass) { - $config = $providersConfig[$providerId] ?? null; - if ($config === null) { + foreach ($providersConfig as $providerId => $config) { + $updateClass = $actions[$providerId] ?? null; + if ($updateClass === null) { continue; } if (!($config['enabled'] ?? false)) { diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php index 08fc7dbf6b..dc9eede2b4 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php @@ -146,13 +146,14 @@ class Update extends Base { $providerId = static::getProviderId(); $oAuthProviders = $project->getAttribute('oAuthProviders', []); + $storedSecret = $this->decodeStoredSecret($project); return new Document([ '$id' => $providerId, 'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false, static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '', - 'keyId' => '', - 'teamId' => '', + 'keyId' => $storedSecret['keyID'] ?? '', + 'teamId' => $storedSecret['teamID'] ?? '', 'p8File' => '', ]); } diff --git a/tests/e2e/Services/Console/ConsoleConsoleClientTest.php b/tests/e2e/Services/Console/ConsoleConsoleClientTest.php index 8235ebb7bc..c111b744c3 100644 --- a/tests/e2e/Services/Console/ConsoleConsoleClientTest.php +++ b/tests/e2e/Services/Console/ConsoleConsoleClientTest.php @@ -56,6 +56,8 @@ class ConsoleConsoleClientTest extends Scope $this->assertEquals($response['body']['total'], \count($response['body']['oAuth2Providers'])); $providerIds = \array_column($response['body']['oAuth2Providers'], '$id'); + $this->assertEquals('amazon', $providerIds[0]); + $this->assertEquals('zoom', $providerIds[\count($providerIds) - 1]); // Well-known providers must be present $this->assertContains('github', $providerIds); diff --git a/tests/e2e/Services/Project/OAuth2Base.php b/tests/e2e/Services/Project/OAuth2Base.php index 8345bfab0a..9ff3830ec5 100644 --- a/tests/e2e/Services/Project/OAuth2Base.php +++ b/tests/e2e/Services/Project/OAuth2Base.php @@ -478,9 +478,8 @@ trait OAuth2Base $this->assertSame(200, $response['headers']['status-code']); $this->assertSame('apple', $response['body']['$id']); $this->assertSame('ip.appwrite.app.web', $response['body']['serviceId']); - // keyId / teamId / p8File are write-only — PATCH response must not echo them back. - $this->assertSame('', $response['body']['keyId']); - $this->assertSame('', $response['body']['teamId']); + $this->assertSame('P4000000N8', $response['body']['keyId']); + $this->assertSame('D4000000R6', $response['body']['teamId']); $this->assertSame('', $response['body']['p8File']); $this->assertSame(false, $response['body']['enabled']); @@ -511,12 +510,10 @@ trait OAuth2Base ]); $this->assertSame(200, $response['headers']['status-code']); - // serviceId is the (non-secret) clientId; keyId/teamId are write-only - // and must not surface in the response. Persistence of the merged - // values is verified separately via the enable-after-merge tests. $this->assertSame('ip.appwrite.app.seed', $response['body']['serviceId']); - $this->assertSame('', $response['body']['keyId']); - $this->assertSame('', $response['body']['teamId']); + $this->assertSame('KEYUPDATED', $response['body']['keyId']); + $this->assertSame('TEAMSEED01', $response['body']['teamId']); + $this->assertSame('', $response['body']['p8File']); // Cleanup $this->updateOAuth2('apple', [ @@ -546,9 +543,9 @@ trait OAuth2Base 'teamId' => 'TEAMROTATED', ]); $this->assertSame(200, $teamOnly['headers']['status-code']); - // teamId is write-only; verify only the non-secret serviceId echo. - // The actual merge is validated by the enable-after-merge call below. - $this->assertSame('', $teamOnly['body']['teamId']); + $this->assertSame('TEAMROTATED', $teamOnly['body']['teamId']); + $this->assertSame('KEYMERGE01', $teamOnly['body']['keyId']); + $this->assertSame('', $teamOnly['body']['p8File']); $this->assertSame('ip.appwrite.app.merge', $teamOnly['body']['serviceId']); // Patch only `serviceId` — keyId/teamId/p8File live in the JSON blob @@ -669,9 +666,8 @@ trait OAuth2Base $this->assertSame(200, $response['headers']['status-code']); $this->assertSame('ip.appwrite.app.read', $response['body']['serviceId']); - // All three secret-bearing fields must be hidden on read. - $this->assertSame('', $response['body']['keyId']); - $this->assertSame('', $response['body']['teamId']); + $this->assertSame('KEYREAD', $response['body']['keyId']); + $this->assertSame('TEAMREAD', $response['body']['teamId']); $this->assertSame('', $response['body']['p8File']); // Cleanup @@ -699,13 +695,13 @@ trait OAuth2Base $this->assertSame(200, $update['headers']['status-code']); $this->assertTrue($update['body']['enabled']); - // GET must hide all three secret-bearing fields while keeping serviceId. + // GET must hide p8File while keeping the non-secret fields. $get = $this->getOAuth2Provider('apple'); $this->assertSame(200, $get['headers']['status-code']); $this->assertTrue($get['body']['enabled']); $this->assertSame('ip.appwrite.app.enable', $get['body']['serviceId']); - $this->assertSame('', $get['body']['keyId']); - $this->assertSame('', $get['body']['teamId']); + $this->assertSame('ENABLEKEY', $get['body']['keyId']); + $this->assertSame('ENABLETEAM', $get['body']['teamId']); $this->assertSame('', $get['body']['p8File']); // Cleanup