From 60b5f4433c619fbb20cc48a8b78fbfd0abb49874 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 24 Mar 2026 16:53:43 +1300 Subject: [PATCH 1/7] (feat): add SSL certificate check step to web installer redirect flow --- .../install/installer/js/modules/context.js | 6 +- .../install/installer/js/modules/progress.js | 124 +++++++++++++++--- .../installer/js/modules/validation.js | 8 +- .../Http/Installer/Certificate/Get.php | 87 ++++++++++++ src/Appwrite/Platform/Installer/Server.php | 1 + .../Platform/Installer/Services/Http.php | 2 + 6 files changed, 206 insertions(+), 22 deletions(-) create mode 100644 src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php diff --git a/app/views/install/installer/js/modules/context.js b/app/views/install/installer/js/modules/context.js index c531ecddce..4917a1bfe9 100644 --- a/app/views/install/installer/js/modules/context.js +++ b/app/views/install/installer/js/modules/context.js @@ -13,7 +13,9 @@ DOCKER_COMPOSE: 'docker-compose', ENV_VARS: 'env-vars', DOCKER_CONTAINERS: 'docker-containers', - ACCOUNT_SETUP: 'account-setup' + ACCOUNT_SETUP: 'account-setup', + SSL_CERTIFICATE: 'ssl-certificate', + REDIRECT: 'redirect' }); const STATUS = Object.freeze({ @@ -75,7 +77,7 @@ { id: STEP_IDS.ACCOUNT_SETUP, inProgress: 'Creating Appwrite account...', - done: 'Appwrite account created (redirecting...)' + done: 'Appwrite account created' } ]); diff --git a/app/views/install/installer/js/modules/progress.js b/app/views/install/installer/js/modules/progress.js index 7f7b23e3fc..712cda052f 100644 --- a/app/views/install/installer/js/modules/progress.js +++ b/app/views/install/installer/js/modules/progress.js @@ -21,7 +21,7 @@ storeInstallId, clearInstallId } = window.InstallerStepsState || {}; - const { extractHostname, isLocalHost } = window.InstallerStepsValidation || {}; + const { extractHostname, isLocalHost, isIPAddress } = window.InstallerStepsValidation || {}; const { generateSecretKey } = window.InstallerStepsUI || {}; const { showToast } = window.InstallerToast || {}; @@ -251,7 +251,7 @@ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(''); }; - const buildRedirectUrl = () => { + const buildRedirectUrl = (protocol) => { const dataset = getBodyDataset?.() ?? {}; const rawDomain = (formState?.appDomain || dataset.defaultAppDomain || '').trim(); if (!rawDomain) return ''; @@ -266,22 +266,44 @@ } else if (normalizedHost === 'traefik') { host = rawDomain.replace(hostForProtocol, 'localhost'); } - let protocol = 'http'; - let port = httpPort; - if (httpsPort && httpsPort !== '0' && !isLocalHost?.(normalizedHost)) { - protocol = 'https'; - port = httpsPort; - } - if (!hasPort && port && ((protocol === 'http' && port !== '80') || (protocol === 'https' && port !== '443'))) { + const port = protocol === 'https' ? httpsPort : httpPort; + const defaultPort = protocol === 'https' ? '443' : '80'; + if (!hasPort && port && port !== defaultPort) { host = `${host}:${port}`; } return `${protocol}://${host}`; }; - const redirectToApp = () => { - const url = buildRedirectUrl(); + const canUseHttps = () => { + const dataset = getBodyDataset?.() ?? {}; + const rawDomain = (formState?.appDomain || dataset.defaultAppDomain || '').trim(); + const httpsPort = (formState?.httpsPort || dataset.defaultHttpsPort || '').trim(); + if (!httpsPort || httpsPort === '0') return false; + const hostname = extractHostname?.(rawDomain)?.toLowerCase?.() ?? ''; + return !isLocalHost?.(hostname) && !isIPAddress?.(hostname); + }; + + const pollCertificate = async (domain, maxAttempts, intervalMs) => { + for (let i = 0; i < maxAttempts; i++) { + try { + const response = await fetch(`/install/certificate?domain=${encodeURIComponent(domain)}`); + if (response.ok) { + const data = await response.json(); + if (data.ready) return true; + } + } catch { + // Installer server may have shut down + } + if (i < maxAttempts - 1) { + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } + } + return false; + }; + + const redirectToApp = (protocol) => { + const url = buildRedirectUrl(protocol); if (!url) return; - // Fire-and-forget: tell the installer server it can shut down fetch('/install/shutdown', { method: 'POST', headers: withCsrfHeader() }).catch(() => {}); window.location.href = url; }; @@ -605,9 +627,7 @@ const accountState = progressState.get(STEP_IDS.ACCOUNT_SETUP); const sessionDetails = sseSessionDetails || accountState?.details; finalizeInstall(); - notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => { - setTimeout(() => redirectToApp(), TIMINGS?.redirectDelay ?? 0); - }); + startSslCheck(sessionDetails); }; const startPolling = () => { @@ -646,6 +666,74 @@ setUnloadGuard(false); }; + const SSL_STEP = { + id: STEP_IDS.SSL_CERTIFICATE, + inProgress: 'Generating SSL certificate...', + done: 'SSL certificate verified' + }; + + const REDIRECT_STEP = { + id: STEP_IDS.REDIRECT, + inProgress: 'Redirecting to console...', + done: 'Redirecting to console...' + }; + + const showRedirectStep = (sessionDetails, protocol) => { + animatePanelHeight(() => { + progressState.set(REDIRECT_STEP.id, { + status: STATUS.IN_PROGRESS, + message: REDIRECT_STEP.inProgress + }); + const row = ensureRow(REDIRECT_STEP); + if (row) { + updateInstallRow(row, REDIRECT_STEP, STATUS.IN_PROGRESS, REDIRECT_STEP.inProgress); + } + }); + startSyncedSpinnerRotation(list); + + notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => { + setTimeout(() => redirectToApp(protocol), TIMINGS?.redirectDelay ?? 0); + }); + }; + + const startSslCheck = (sessionDetails) => { + if (!canUseHttps()) { + showRedirectStep(sessionDetails, 'http'); + return; + } + + animatePanelHeight(() => { + progressState.set(SSL_STEP.id, { + status: STATUS.IN_PROGRESS, + message: SSL_STEP.inProgress + }); + const row = ensureRow(SSL_STEP); + if (row) { + updateInstallRow(row, SSL_STEP, STATUS.IN_PROGRESS, SSL_STEP.inProgress); + } + }); + startSyncedSpinnerRotation(list); + + const dataset = getBodyDataset?.() ?? {}; + const rawDomain = (formState?.appDomain || dataset.defaultAppDomain || '').trim(); + const domain = extractHostname?.(rawDomain) || rawDomain; + pollCertificate(domain, 15, 2000).then((ready) => { + stopSyncedSpinnerRotation(); + const certMessage = ready ? SSL_STEP.done : 'Certificate pending'; + animatePanelHeight(() => { + progressState.set(SSL_STEP.id, { + status: STATUS.COMPLETED, + message: certMessage + }); + const row = ensureRow(SSL_STEP); + if (row) { + updateInstallRow(row, SSL_STEP, STATUS.COMPLETED, certMessage); + } + }); + showRedirectStep(sessionDetails, ready ? 'https' : 'http'); + }); + }; + const startInstallStream = async (installId, options = {}) => { const isValid = await validateInstallRequest(); if (!isValid) { @@ -746,9 +834,7 @@ const accountState = progressState.get(STEP_IDS.ACCOUNT_SETUP); const sessionDetails = sseSessionDetails || accountState?.details; finalizeInstall(); - notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => { - setTimeout(() => redirectToApp(), TIMINGS?.redirectDelay ?? 0); - }); + startSslCheck(sessionDetails); return; } if (event === SSE_EVENTS.ERROR) { @@ -857,7 +943,7 @@ const retryButton = event.target.closest('[data-install-retry]'); if (consoleButton) { - redirectToApp(); + redirectToApp('http'); return; } diff --git a/app/views/install/installer/js/modules/validation.js b/app/views/install/installer/js/modules/validation.js index 13ab60ef4e..daa66eb8d6 100644 --- a/app/views/install/installer/js/modules/validation.js +++ b/app/views/install/installer/js/modules/validation.js @@ -106,12 +106,18 @@ return LOCAL_HOSTS.has(normalized); }; + const isIPAddress = (host) => { + if (!host) return false; + return isValidIPv4(host) || isValidIPv6(host); + }; + window.InstallerStepsValidation = { isValidEmail, isValidPort, isValidPassword, isValidHostnameInput, extractHostname, - isLocalHost + isLocalHost, + isIPAddress }; })(); diff --git a/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php b/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php new file mode 100644 index 0000000000..eb18685683 --- /dev/null +++ b/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php @@ -0,0 +1,87 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/install/certificate') + ->desc('Check if SSL certificate is ready for a domain') + ->param('domain', '', new AppDomain(), 'Domain to check') + ->inject('response') + ->callback($this->action(...)); + } + + public function action(string $domain, Response $response): void + { + $domain = trim($domain); + if ($domain === '') { + $response->json(['ready' => false]); + return; + } + + $ready = $this->checkHttps($domain); + $response->json(['ready' => $ready]); + } + + private function checkHttps(string $domain): bool + { + $gateway = $this->getDockerGateway(); + $port = 443; + + $ch = curl_init(); + $options = [ + CURLOPT_URL => 'https://' . $domain . ':' . $port . '/', + CURLOPT_NOBODY => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CONNECTTIMEOUT => self::CONNECTION_TIMEOUT_SECONDS, + CURLOPT_TIMEOUT => self::CONNECTION_TIMEOUT_SECONDS, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_SSL_VERIFYHOST => 2, + ]; + + if ($gateway !== '') { + $options[CURLOPT_RESOLVE] = [$domain . ':' . $port . ':' . $gateway]; + } + + curl_setopt_array($ch, $options); + curl_exec($ch); + $errno = curl_errno($ch); + curl_close($ch); + + return $errno === 0; + } + + private function getDockerGateway(): string + { + $route = @file_get_contents('/proc/net/route'); + if ($route === false) { + return ''; + } + + foreach (explode("\n", $route) as $line) { + $fields = preg_split('/\s+/', trim($line)); + if (isset($fields[1]) && $fields[1] === '00000000' && isset($fields[2])) { + $hex = $fields[2]; + $ip = long2ip((int) hexdec($hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1])); + return $ip; + } + } + + return ''; + } +} diff --git a/src/Appwrite/Platform/Installer/Server.php b/src/Appwrite/Platform/Installer/Server.php index f36c270553..67dc433812 100644 --- a/src/Appwrite/Platform/Installer/Server.php +++ b/src/Appwrite/Platform/Installer/Server.php @@ -27,6 +27,7 @@ class Server public const string STEP_DOCKER_COMPOSE = 'docker-compose'; public const string STEP_DOCKER_CONTAINERS = 'docker-containers'; public const string STEP_ACCOUNT_SETUP = 'account-setup'; + public const string STEP_SSL_CERTIFICATE = 'ssl-certificate'; public const string STATUS_IN_PROGRESS = 'in-progress'; public const string STATUS_COMPLETED = 'completed'; diff --git a/src/Appwrite/Platform/Installer/Services/Http.php b/src/Appwrite/Platform/Installer/Services/Http.php index bd0fc62cdc..0de977b177 100644 --- a/src/Appwrite/Platform/Installer/Services/Http.php +++ b/src/Appwrite/Platform/Installer/Services/Http.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Installer\Services; +use Appwrite\Platform\Installer\Http\Installer\Certificate\Get as CertificateGet; use Appwrite\Platform\Installer\Http\Installer\Complete; use Appwrite\Platform\Installer\Http\Installer\Install; use Appwrite\Platform\Installer\Http\Installer\Shutdown; @@ -22,5 +23,6 @@ class Http extends Service $this->addAction(Complete::getName(), new Complete()); $this->addAction(Shutdown::getName(), new Shutdown()); $this->addAction(Install::getName(), new Install()); + $this->addAction(CertificateGet::getName(), new CertificateGet()); } } From e58f0b6378df088e4b7a5a8272e8d97735f3eec0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 24 Mar 2026 17:46:22 +1300 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20pass?= =?UTF-8?q?=20HTTPS=20port=20to=20certificate=20check,=20use=20resolved=20?= =?UTF-8?q?protocol=20for=20console=20button,=20add=20hex=20length=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/install/installer/js/modules/progress.js | 13 ++++++++----- .../Installer/Http/Installer/Certificate/Get.php | 12 ++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/views/install/installer/js/modules/progress.js b/app/views/install/installer/js/modules/progress.js index 712cda052f..53d7f49a8b 100644 --- a/app/views/install/installer/js/modules/progress.js +++ b/app/views/install/installer/js/modules/progress.js @@ -283,10 +283,10 @@ return !isLocalHost?.(hostname) && !isIPAddress?.(hostname); }; - const pollCertificate = async (domain, maxAttempts, intervalMs) => { + const pollCertificate = async (domain, port, maxAttempts, intervalMs) => { for (let i = 0; i < maxAttempts; i++) { try { - const response = await fetch(`/install/certificate?domain=${encodeURIComponent(domain)}`); + const response = await fetch(`/install/certificate?domain=${encodeURIComponent(domain)}&port=${encodeURIComponent(port)}`); if (response.ok) { const data = await response.json(); if (data.ready) return true; @@ -428,6 +428,7 @@ const initStep5 = (root) => { if (!root) return; + let resolvedProtocol = 'http'; if (activeInstall?.controller) { activeInstall.controller.abort(); @@ -716,8 +717,9 @@ const dataset = getBodyDataset?.() ?? {}; const rawDomain = (formState?.appDomain || dataset.defaultAppDomain || '').trim(); + const httpsPort = (formState?.httpsPort || dataset.defaultHttpsPort || '443').trim(); const domain = extractHostname?.(rawDomain) || rawDomain; - pollCertificate(domain, 15, 2000).then((ready) => { + pollCertificate(domain, httpsPort, 15, 2000).then((ready) => { stopSyncedSpinnerRotation(); const certMessage = ready ? SSL_STEP.done : 'Certificate pending'; animatePanelHeight(() => { @@ -730,7 +732,8 @@ updateInstallRow(row, SSL_STEP, STATUS.COMPLETED, certMessage); } }); - showRedirectStep(sessionDetails, ready ? 'https' : 'http'); + resolvedProtocol = ready ? 'https' : 'http'; + showRedirectStep(sessionDetails, resolvedProtocol); }); }; @@ -943,7 +946,7 @@ const retryButton = event.target.closest('[data-install-retry]'); if (consoleButton) { - redirectToApp('http'); + redirectToApp(resolvedProtocol); return; } diff --git a/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php b/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php index eb18685683..ab0037f4b2 100644 --- a/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php +++ b/src/Appwrite/Platform/Installer/Http/Installer/Certificate/Get.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Installer\Http\Installer\Certificate; use Appwrite\Platform\Installer\Validator\AppDomain; use Utopia\Http\Adapter\Swoole\Response; use Utopia\Platform\Action; +use Utopia\Validator\Range; class Get extends Action { @@ -22,11 +23,12 @@ class Get extends Action ->setHttpPath('/install/certificate') ->desc('Check if SSL certificate is ready for a domain') ->param('domain', '', new AppDomain(), 'Domain to check') + ->param('port', 443, new Range(1, 65535), 'HTTPS port to check', true) ->inject('response') ->callback($this->action(...)); } - public function action(string $domain, Response $response): void + public function action(string $domain, int $port, Response $response): void { $domain = trim($domain); if ($domain === '') { @@ -34,14 +36,13 @@ class Get extends Action return; } - $ready = $this->checkHttps($domain); + $ready = $this->checkHttps($domain, $port); $response->json(['ready' => $ready]); } - private function checkHttps(string $domain): bool + private function checkHttps(string $domain, int $port): bool { $gateway = $this->getDockerGateway(); - $port = 443; $ch = curl_init(); $options = [ @@ -77,6 +78,9 @@ class Get extends Action $fields = preg_split('/\s+/', trim($line)); if (isset($fields[1]) && $fields[1] === '00000000' && isset($fields[2])) { $hex = $fields[2]; + if (strlen($hex) !== 8) { + continue; + } $ip = long2ip((int) hexdec($hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1])); return $ip; } From a1441174f2c328bbfe8483b326ccb060e5fad589 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 24 Mar 2026 17:57:58 +1300 Subject: [PATCH 3/7] fix: update installer module test to expect 7 actions including CertificateGet --- tests/unit/Platform/Modules/Installer/ModuleTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/Platform/Modules/Installer/ModuleTest.php b/tests/unit/Platform/Modules/Installer/ModuleTest.php index 8df452d8de..f3b4b9d9ae 100644 --- a/tests/unit/Platform/Modules/Installer/ModuleTest.php +++ b/tests/unit/Platform/Modules/Installer/ModuleTest.php @@ -41,13 +41,14 @@ class ModuleTest extends TestCase $service = reset($services); $actions = $service->getActions(); - $this->assertCount(6, $actions); + $this->assertCount(7, $actions); $this->assertArrayHasKey('installerView', $actions); $this->assertArrayHasKey('installerStatus', $actions); $this->assertArrayHasKey('installerValidate', $actions); $this->assertArrayHasKey('installerComplete', $actions); $this->assertArrayHasKey('installerShutdown', $actions); $this->assertArrayHasKey('installerInstall', $actions); + $this->assertArrayHasKey('installerCertificateGet', $actions); } public function testViewAction(): void From 5b5020fda4203a0b3e1984559f138f8c71592939 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 24 Mar 2026 18:18:56 +1300 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20norma?= =?UTF-8?q?lize=20hostname=20for=20cert=20check,=20add=20cache=20bypass,?= =?UTF-8?q?=20improve=20fallback=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../install/installer/js/modules/progress.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/views/install/installer/js/modules/progress.js b/app/views/install/installer/js/modules/progress.js index 53d7f49a8b..bb1fa2f551 100644 --- a/app/views/install/installer/js/modules/progress.js +++ b/app/views/install/installer/js/modules/progress.js @@ -274,19 +274,28 @@ return `${protocol}://${host}`; }; + const normalizeHostname = (rawDomain) => { + const hostname = extractHostname?.(rawDomain)?.toLowerCase?.() ?? ''; + if (hostname === '0.0.0.0' || hostname === 'traefik') return 'localhost'; + return hostname; + }; + const canUseHttps = () => { const dataset = getBodyDataset?.() ?? {}; const rawDomain = (formState?.appDomain || dataset.defaultAppDomain || '').trim(); const httpsPort = (formState?.httpsPort || dataset.defaultHttpsPort || '').trim(); if (!httpsPort || httpsPort === '0') return false; - const hostname = extractHostname?.(rawDomain)?.toLowerCase?.() ?? ''; + const hostname = normalizeHostname(rawDomain); return !isLocalHost?.(hostname) && !isIPAddress?.(hostname); }; const pollCertificate = async (domain, port, maxAttempts, intervalMs) => { for (let i = 0; i < maxAttempts; i++) { try { - const response = await fetch(`/install/certificate?domain=${encodeURIComponent(domain)}&port=${encodeURIComponent(port)}`); + const response = await fetch( + `/install/certificate?domain=${encodeURIComponent(domain)}&port=${encodeURIComponent(port)}`, + { cache: 'no-store' } + ); if (response.ok) { const data = await response.json(); if (data.ready) return true; @@ -718,10 +727,10 @@ const dataset = getBodyDataset?.() ?? {}; const rawDomain = (formState?.appDomain || dataset.defaultAppDomain || '').trim(); const httpsPort = (formState?.httpsPort || dataset.defaultHttpsPort || '443').trim(); - const domain = extractHostname?.(rawDomain) || rawDomain; + const domain = normalizeHostname(rawDomain); pollCertificate(domain, httpsPort, 15, 2000).then((ready) => { stopSyncedSpinnerRotation(); - const certMessage = ready ? SSL_STEP.done : 'Certificate pending'; + const certMessage = ready ? SSL_STEP.done : 'Certificate not ready, continuing over HTTP'; animatePanelHeight(() => { progressState.set(SSL_STEP.id, { status: STATUS.COMPLETED, From 20bd7af370419140c7f2802d6340f5d52f2df163 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 24 Mar 2026 15:59:42 +0530 Subject: [PATCH 5/7] added a fallback isnulll --- .../Modules/Databases/Http/Databases/XList.php | 6 ++---- .../Modules/Databases/Http/TablesDB/XList.php | 12 +++++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php index 7ff1a27de7..361ea59176 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php @@ -29,7 +29,7 @@ class XList extends Action protected function getDatabaseTypeQueryFilters(): array { - return [$this->getDatabaseType()]; + return [Query::equal('type', [$this->getDatabaseType()])]; } public function __construct() @@ -96,10 +96,8 @@ class XList extends Action $cursor->setValue($cursorDocument); } - $queries[] = Query::equal('type', $this->getDatabaseTypeQueryFilters()); - try { - $databases = $dbForProject->find('databases', $queries); + $databases = $dbForProject->find('databases', $this->getDatabaseTypeQueryFilters()); $total = $includeTotal ? $dbForProject->count('databases', $queries, APP_LIMIT_COUNT) : 0; } catch (OrderException $e) { throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order column '{$e->getAttribute()}' had a null value. Cursor pagination requires all rows order column values are non-null."); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php index a9358f3f63..8d84f5b1c8 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php @@ -9,6 +9,7 @@ use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Database\Validator\Queries\Databases; use Appwrite\Utopia\Response as UtopiaResponse; +use Utopia\Database\Query; use Utopia\Http\Adapter\Swoole\Response as SwooleResponse; use Utopia\Validator\Boolean; use Utopia\Validator\Text; @@ -21,9 +22,14 @@ class XList extends DatabaseXList } protected function getDatabaseTypeQueryFilters(): array - { - return [DATABASE_TYPE_TABLESDB, DATABASE_TYPE_LEGACY]; - } +{ + return [ + Query::or([ + Query::equal('type', [DATABASE_TYPE_TABLESDB, DATABASE_TYPE_LEGACY]), + Query::isNull('type'), + ]), + ]; +} public function __construct() { From 9e595588bcbd2645ed116fc61b4d2d92e00a32eb Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 24 Mar 2026 16:03:28 +0530 Subject: [PATCH 6/7] lint --- .../Modules/Databases/Http/TablesDB/XList.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php index 8d84f5b1c8..8dc0f6521a 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/TablesDB/XList.php @@ -22,14 +22,14 @@ class XList extends DatabaseXList } protected function getDatabaseTypeQueryFilters(): array -{ - return [ - Query::or([ - Query::equal('type', [DATABASE_TYPE_TABLESDB, DATABASE_TYPE_LEGACY]), - Query::isNull('type'), - ]), - ]; -} + { + return [ + Query::or([ + Query::equal('type', [DATABASE_TYPE_TABLESDB, DATABASE_TYPE_LEGACY]), + Query::isNull('type'), + ]), + ]; + } public function __construct() { From 2b33dc3c7228715992b427191741c54ee5f1a3fe Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 24 Mar 2026 16:07:31 +0530 Subject: [PATCH 7/7] updated merging of user and current queries --- .../Platform/Modules/Databases/Http/Databases/XList.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php index 361ea59176..21dbc83edc 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/XList.php @@ -97,7 +97,8 @@ class XList extends Action } try { - $databases = $dbForProject->find('databases', $this->getDatabaseTypeQueryFilters()); + $queries = array_merge($queries, $this->getDatabaseTypeQueryFilters()); + $databases = $dbForProject->find('databases', $queries); $total = $includeTotal ? $dbForProject->count('databases', $queries, APP_LIMIT_COUNT) : 0; } catch (OrderException $e) { throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order column '{$e->getAttribute()}' had a null value. Cursor pagination requires all rows order column values are non-null.");