diff --git a/.env b/.env index 726043414a..fee68d5a04 100644 --- a/.env +++ b/.env @@ -133,4 +133,5 @@ _APP_PROJECT_REGIONS=default _APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000 _APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main _APP_TRUSTED_HEADERS=x-forwarded-for -_APP_POOL_ADAPTER=stack \ No newline at end of file +_APP_POOL_ADAPTER=stack +_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 62b4953e27..b7b4fa0d2f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,6 +3,7 @@ concurrency: group: '${{ github.workflow }}-${{ github.ref }}' cancel-in-progress: true env: + COMPOSE_FILE: docker-compose.yml IMAGE: appwrite-dev CACHE_KEY: 'appwrite-dev-${{ github.event.pull_request.head.sha }}' 'on': @@ -13,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive - name: Set up Docker Buildx @@ -28,6 +29,7 @@ jobs: cache-from: type=gha cache-to: 'type=gha,mode=max' outputs: 'type=docker,dest=/tmp/${{ env.IMAGE }}.tar' + target: development build-args: | DEBUG=false TESTING=true @@ -45,7 +47,7 @@ jobs: pull-requests: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 with: @@ -97,7 +99,7 @@ jobs: echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt - name: Save results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: '${{ !cancelled() }}' with: name: benchmark.json diff --git a/.github/workflows/cleanup-cache.yml b/.github/workflows/cleanup-cache.yml index 8f25fe5ef6..8f9f05a38c 100644 --- a/.github/workflows/cleanup-cache.yml +++ b/.github/workflows/cleanup-cache.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Cleanup run: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a40f07ceda..7edfde0aae 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 02edd57923..f4ae5df1ce 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 @@ -20,9 +20,9 @@ jobs: - name: Validate composer.json and composer.lock run: | - docker run --rm -v $PWD:/app composer sh -c \ + docker run --rm -v $PWD:/app composer:2.8 sh -c \ "composer validate" - name: Run Linter run: | - docker run --rm -v $PWD:/app composer sh -c \ + docker run --rm -v $PWD:/app composer:2.8 sh -c \ "composer install --profile --ignore-platform-reqs && composer lint" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 80d880244c..cd9b3827e7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive - name: Build the Docker image - run: docker build . -t appwrite_image:latest + run: DOCKER_BUILDKIT=1 docker build . --target production -t appwrite_image:latest - name: Run Trivy vulnerability scanner on image uses: aquasecurity/trivy-action@0.20.0 with: @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Run Trivy vulnerability scanner on filesystem uses: aquasecurity/trivy-action@0.20.0 with: diff --git a/.github/workflows/pr-scan.yml b/.github/workflows/pr-scan.yml index eded58985d..51f3460d03 100644 --- a/.github/workflows/pr-scan.yml +++ b/.github/workflows/pr-scan.yml @@ -11,19 +11,20 @@ jobs: pull-requests: write steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 submodules: 'recursive' - name: Build the Docker image - uses: docker/build-push-action@v5 - with: + uses: docker/build-push-action@v6 + with: context: . push: false load: true tags: pr_image:${{ github.sha }} + target: production - name: Run Trivy vulnerability scanner on image uses: aquasecurity/trivy-action@0.20.0 @@ -44,7 +45,7 @@ jobs: - name: Process Trivy scan results id: process-results - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const fs = require('fs'); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5579317e90..692861d44d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 2 submodules: recursive @@ -38,7 +38,7 @@ jobs: type=ref,event=tag - name: Build & Publish to DockerHub - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf09663a06..84fc4c9fba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -42,7 +42,7 @@ jobs: type=semver,pattern={{major}} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/sdk-preview.yml b/.github/workflows/sdk-preview.yml index e317845768..986a77399d 100644 --- a/.github/workflows/sdk-preview.yml +++ b/.github/workflows/sdk-preview.yml @@ -1,5 +1,8 @@ name: "SDK Preview" +env: + COMPOSE_FILE: docker-compose.yml + on: pull_request: paths: @@ -19,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set SDK type id: set-sdk diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 1c5f36ace3..a0dc38b3b4 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -8,11 +8,11 @@ jobs: steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Run CodeQL run: | - docker run --rm -v $PWD:/app composer:2.6 sh -c \ + docker run --rm -v $PWD:/app composer:2.8 sh -c \ "composer install --profile --ignore-platform-reqs && composer check" - name: Run Locale check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fc678a72da..4bb2662ab6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,7 @@ concurrency: cancel-in-progress: true env: + COMPOSE_FILE: docker-compose.yml IMAGE: appwrite-dev CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }} @@ -26,7 +27,7 @@ jobs: database_changed: ${{ steps.check.outputs.database_changed }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Fetch base branch run: git fetch origin ${{ github.event.pull_request.base.ref }} @@ -48,7 +49,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive @@ -65,6 +66,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar + target: development build-args: | DEBUG=false TESTING=true @@ -86,7 +88,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -132,7 +134,7 @@ jobs: pull-requests: write steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -217,7 +219,7 @@ jobs: ] steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -332,7 +334,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -397,7 +399,7 @@ jobs: pull-requests: write steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -457,7 +459,7 @@ jobs: ] steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -515,7 +517,7 @@ jobs: pull-requests: write steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 @@ -576,7 +578,7 @@ jobs: ] steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v4 diff --git a/app/cli.php b/app/cli.php index 14ebea6e1c..0f8426afd9 100644 --- a/app/cli.php +++ b/app/cli.php @@ -30,9 +30,6 @@ use Utopia\Pools\Group; use Utopia\Queue\Broker\Pool as BrokerPool; use Utopia\Queue\Publisher; use Utopia\Registry\Registry; -use Utopia\Span\Exporter; -use Utopia\Span\Span; -use Utopia\Span\Storage; use Utopia\System\System; use Utopia\Telemetry\Adapter\None as NoTelemetry; @@ -340,6 +337,5 @@ $cli $cli->shutdown()->action(fn () => Timer::clearAll()); Runtime::enableCoroutine(SWOOLE_HOOK_ALL); -Span::setStorage(new Storage\Coroutine()); -Span::addExporter(new Exporter\Stdout()); +require_once __DIR__ . '/init/span.php'; run($cli->run(...)); diff --git a/app/controllers/general.php b/app/controllers/general.php index a4f364485e..2d3bde69d6 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -52,6 +52,7 @@ use Utopia\Logger\Log; use Utopia\Logger\Log\User; use Utopia\Logger\Logger; use Utopia\Platform\Service; +use Utopia\Span\Span; use Utopia\System\System; use Utopia\Validator; use Utopia\Validator\Text; @@ -1245,17 +1246,7 @@ Http::error() $trace = $error->getTrace(); if (php_sapi_name() === 'cli') { - Console::error('[Error] Timestamp: ' . date('c', time())); - - if ($route) { - Console::error('[Error] Method: ' . $route->getMethod()); - Console::error('[Error] URL: ' . $route->getPath()); - } - - Console::error('[Error] Type: ' . get_class($error)); - Console::error('[Error] Message: ' . $message); - Console::error('[Error] File: ' . $file); - Console::error('[Error] Line: ' . $line); + Span::error($error); } switch ($class) { diff --git a/app/http.php b/app/http.php index d5af8eacea..1d7949da86 100644 --- a/app/http.php +++ b/app/http.php @@ -1,6 +1,7 @@ on(Constant::EVENT_WORKER_START, function ($server, $workerId) use (&$fil $files = new Files(); $files->load(__DIR__ . '/../public'); } - Console::success('Worker ' . ++$workerId . ' started successfully'); }); $http->on(Constant::EVENT_WORKER_STOP, function ($server, $workerId) { @@ -207,17 +208,18 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c } } - Console::success("[Setup] - $dbName database init started..."); + Span::init("database.setup"); + Span::add('database.name', $dbName); // Attempt to create the database try { - Console::info(" └── Creating database: $dbName..."); $database->create(); } catch (\Exception $e) { - Console::info(" └── Skip: metadata table already exists"); + Span::add('database.exists', true); } // Process collections + $collectionsCreated = 0; foreach ($collections as $key => $collection) { if (($collection['$collection'] ?? '') !== Database::METADATA) { continue; @@ -227,8 +229,6 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c continue; } - Console::info(" └── Creating collection: {$collection['$id']}..."); - $attributes = array_map(fn ($attr) => new Document([ '$id' => ID::custom($attr['$id']), 'type' => $attr['type'], @@ -250,14 +250,19 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c ]), $collection['indexes']); $database->createCollection($key, $attributes, $indexes); + $collectionsCreated++; } + Span::add('database.collections_created', $collectionsCreated); + if ($extraSetup) { $extraSetup($database); } + + Span::current()?->finish(); } -$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) { +$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $totalWorkers, $register) { $app = new Http('UTC'); go(function () use ($register, $app) { @@ -282,7 +287,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg } if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty()) { - Console::info(" └── Creating default bucket..."); $dbForPlatform->createDocument('buckets', new Document([ '$id' => ID::custom('default'), '$collection' => ID::custom('buckets'), @@ -305,7 +309,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg $bucket = $dbForPlatform->getDocument('buckets', 'default'); - Console::info(" └── Creating files collection for default bucket..."); $files = $collections['buckets']['files'] ?? []; if (empty($files)) { throw new Exception('Files collection is not configured.'); @@ -335,7 +338,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg } if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) { - Console::info(" └── Creating screenshots bucket..."); $authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([ '$id' => ID::custom('screenshots'), '$collection' => ID::custom('buckets'), @@ -353,7 +355,6 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg $bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')); - Console::info(" └── Creating files collection for screenshots bucket..."); $files = $collections['buckets']['files'] ?? []; if (empty($files)) { throw new Exception('Files collection is not configured.'); @@ -391,6 +392,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg $cache = $app->getResource('cache'); foreach ($sharedTablesV2 as $hostname) { + Span::init('database.setup'); + Span::add('database.hostname', $hostname); + $adapter = new DatabasePool($pools->get($hostname)); $dbForProject = (new Database($adapter, $cache)) ->setDatabase('appwrite') @@ -399,10 +403,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg ->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '')); try { - Console::success('[Setup] - Creating project database: ' . $hostname . '...'); $dbForProject->create(); } catch (DuplicateException) { - Console::success('[Setup] - Skip: metadata table already exists'); + Span::add('database.exists', true); } if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) { @@ -411,6 +414,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg $audit->setup(); } + $collectionsCreated = 0; foreach ($projectCollections as $key => $collection) { if (($collection['$collection'] ?? '') !== Database::METADATA) { continue; @@ -422,17 +426,21 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg $attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']); $indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']); - Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...'); - $dbForProject->createCollection($key, $attributes, $indexes); + $collectionsCreated++; } - } - Console::success('[Setup] - Server database init completed...'); + Span::add('database.collections_created', $collectionsCreated); + Span::current()?->finish(); + } }); - Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)'); - Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); + Span::init('http.server.start'); + Span::add('server.workers', $totalWorkers); + Span::add('server.payload_size', $payloadSize); + Span::add('server.master_pid', $http->master_pid); + Span::add('server.manager_pid', $http->manager_pid); + Span::current()?->finish(); // Start the task that starts fetching custom domains $http->task([], 0); @@ -445,12 +453,16 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg }); $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, &$files) { + Span::init('http.request'); + Http::setResource('swooleRequest', fn () => $swooleRequest); Http::setResource('swooleResponse', fn () => $swooleResponse); $request = new Request($swooleRequest); $response = new Response($swooleResponse); + Span::add('http.method', $request->getMethod()); + if ($files instanceof Files && $files->isFileLoaded($request->getURI())) { $time = (60 * 60 * 24 * 45); // 45 days cache @@ -479,7 +491,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool $authorization->addRole(Role::any()->toString()); $app->run($request, $response); + + $route = $app->getRoute(); + Span::add('http.path', $route?->getPath() ?? 'unknown'); } catch (\Throwable $th) { + Span::error($th); + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); $logger = $app->getResource("logger"); @@ -542,12 +559,6 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool } } - Console::error('[Error] Type: ' . get_class($th)); - Console::error('[Error] Message: ' . $th->getMessage()); - Console::error('[Error] File: ' . $th->getFile()); - Console::error('[Error] Line: ' . $th->getLine()); - Console::error('[Error] Trace: ' . $th->getTraceAsString()); - $swooleResponse->setStatusCode(500); $output = ((Http::isDevelopment())) ? [ @@ -564,6 +575,9 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool ]; $swooleResponse->end(\json_encode($output)); + } finally { + Span::add('http.response.code', $response->getStatusCode()); + Span::current()?->finish(); } }); diff --git a/app/init/span.php b/app/init/span.php new file mode 100644 index 0000000000..76f37f5300 --- /dev/null +++ b/app/init/span.php @@ -0,0 +1,8 @@ + $register); diff --git a/composer.json b/composer.json index c217b63e6f..15346eb169 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "utopia-php/detector": "0.2.*", "utopia-php/domains": "1.*", "utopia-php/emails": "0.6.*", - "utopia-php/dns": "1.5.*", + "utopia-php/dns": "1.6.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", "utopia-php/fetch": "0.5.*", @@ -68,6 +68,7 @@ "utopia-php/migration": "1.5.*", "utopia-php/platform": "0.7.*", "utopia-php/pools": "1.*", + "utopia-php/span": "1.1.*", "utopia-php/preloader": "0.2.*", "utopia-php/queue": "0.15.*", "utopia-php/registry": "0.5.*", @@ -91,7 +92,7 @@ "appwrite/sdk-generator": "*", "phpunit/phpunit": "9.*", "swoole/ide-helper": "6.*", - "phpstan/phpstan": "1.8.*", + "phpstan/phpstan": "1.12.*", "textalk/websocket": "1.5.*", "laravel/pint": "1.*", "phpbench/phpbench": "1.*" diff --git a/composer.lock b/composer.lock index ec5fe97db7..f522fc29ca 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": "57189be7990142e6a44a13eefdfe3043", + "content-hash": "9397ae16877660a3ea485cfdcaab906c", "packages": [ { "name": "adhocore/jwt", @@ -3948,22 +3948,22 @@ }, { "name": "utopia-php/dns", - "version": "1.5.4", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/utopia-php/dns.git", - "reference": "ee831a6f2ceb28babb042ea65539c26ea4530bf6" + "reference": "98c70520213a41e2fe1867e5b110273c06bf1cab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/dns/zipball/ee831a6f2ceb28babb042ea65539c26ea4530bf6", - "reference": "ee831a6f2ceb28babb042ea65539c26ea4530bf6", + "url": "https://api.github.com/repos/utopia-php/dns/zipball/98c70520213a41e2fe1867e5b110273c06bf1cab", + "reference": "98c70520213a41e2fe1867e5b110273c06bf1cab", "shasum": "" }, "require": { "php": ">=8.3", "utopia-php/domains": "1.0.*", - "utopia-php/span": "1.0.*", + "utopia-php/span": "1.1.*", "utopia-php/telemetry": "*", "utopia-php/validators": "0.*" }, @@ -3999,9 +3999,9 @@ ], "support": { "issues": "https://github.com/utopia-php/dns/issues", - "source": "https://github.com/utopia-php/dns/tree/1.5.4" + "source": "https://github.com/utopia-php/dns/tree/1.6.2" }, - "time": "2026-02-02T10:40:38+00:00" + "time": "2026-02-13T12:29:08+00:00" }, { "name": "utopia-php/domains", @@ -4909,25 +4909,26 @@ }, { "name": "utopia-php/span", - "version": "1.0.0", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/utopia-php/span.git", - "reference": "f2f6c499ded3a776e8019902e83d140ff0f89693" + "reference": "49d04aa588a2cdbbc9381ee7a1c129469e0f905c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/span/zipball/f2f6c499ded3a776e8019902e83d140ff0f89693", - "reference": "f2f6c499ded3a776e8019902e83d140ff0f89693", + "url": "https://api.github.com/repos/utopia-php/span/zipball/49d04aa588a2cdbbc9381ee7a1c129469e0f905c", + "reference": "49d04aa588a2cdbbc9381ee7a1c129469e0f905c", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "laravel/pint": "^1.0", "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^10.0", + "rector/rector": "^2.3", "swoole/ide-helper": "^5.0" }, "suggest": { @@ -4946,9 +4947,9 @@ "description": "Simple span tracing library for PHP with coroutine support", "support": { "issues": "https://github.com/utopia-php/span/issues", - "source": "https://github.com/utopia-php/span/tree/1.0.0" + "source": "https://github.com/utopia-php/span/tree/1.1.4" }, - "time": "2026-01-12T20:05:10+00:00" + "time": "2026-02-13T10:58:12+00:00" }, { "name": "utopia-php/storage", @@ -6238,16 +6239,11 @@ }, { "name": "phpstan/phpstan", - "version": "1.8.11", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "46e223dd68a620da18855c23046ddb00940b4014" - }, + "version": "1.12.32", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", - "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8", + "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8", "shasum": "" }, "require": { @@ -6276,8 +6272,11 @@ "static analysis" ], "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { @@ -6287,13 +6286,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2022-10-24T15:45:13+00:00" + "time": "2025-09-30T10:16:31+00:00" }, { "name": "phpunit/php-code-coverage", @@ -8919,5 +8914,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 0a959cd137..5a6367f402 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -564,6 +564,7 @@ services: environment: # Specific - _APP_BROWSER_HOST + - _APP_WORKER_SCREENSHOTS_ROUTER # Basic - _APP_ENV - _APP_WORKER_PER_CORE diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php index 0c497df6ea..4f1c7822ec 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php @@ -301,7 +301,7 @@ class Create extends Base if ($async) { if (is_null($scheduledAt)) { - if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check + if ($project->getId() != '6862e6a6000cce69f9da') { $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); } $queueForFunctions @@ -344,7 +344,7 @@ class Create extends Base ->setAttribute('scheduleInternalId', $schedule->getSequence()) ->setAttribute('scheduledAt', $scheduledAt); - if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check + if ($project->getId() != '6862e6a6000cce69f9da') { $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); } } @@ -505,7 +505,7 @@ class Create extends Base ->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT))) ; - if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check + if ($project->getId() != '6862e6a6000cce69f9da') { $execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution)); } } diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php b/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php index ff0fa812cf..f5f7a7974b 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Screenshots.php @@ -111,15 +111,16 @@ class Screenshots extends Action throw new \Exception('Bucket not found'); } + $routerHost = System::getEnv('_APP_WORKER_SCREENSHOTS_ROUTER', 'http://appwrite'); $configs = [ 'screenshotLight' => [ 'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ], - 'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=light', + 'url' => $routerHost . '/?appwrite-preview=1&appwrite-theme=light', 'theme' => 'light' ], 'screenshotDark' => [ 'headers' => [ 'x-appwrite-hostname' => $rule->getAttribute('domain') ], - 'url' => 'http://appwrite/?appwrite-preview=1&appwrite-theme=dark', + 'url' => $routerHost . '/?appwrite-preview=1&appwrite-theme=dark', 'theme' => 'dark' ], ]; diff --git a/src/Appwrite/Platform/Workers/Executions.php b/src/Appwrite/Platform/Workers/Executions.php index 300a84162c..d874e26267 100644 --- a/src/Appwrite/Platform/Workers/Executions.php +++ b/src/Appwrite/Platform/Workers/Executions.php @@ -7,7 +7,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Platform\Action; use Utopia\Queue\Message; -use Utopia\System\System; class Executions extends Action { @@ -45,7 +44,8 @@ class Executions extends Action throw new Exception('Missing execution'); } - if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check + $project = new Document($payload['project'] ?? []); + if ($project->getId() != '6862e6a6000cce69f9da') { $dbForProject->upsertDocument('executions', $execution); } } diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 6b81713654..ccdfb84e00 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -185,6 +185,14 @@ class Client $responseHeaders = []; $cookies = []; + if (isset($params['queries'])) { + foreach ($params['queries'] as $value) { + if (!is_string($value)) { + throw new Exception('Queries must be converted to strings'); + } + } + } + $query = match ($headers['content-type']) { 'application/json' => json_encode($params), 'multipart/form-data' => $this->flatten($params), diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index 9f59bf5922..c6944089c0 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -2430,22 +2430,36 @@ trait DatabasesBase $this->assertEquals($document['title'], $response['body']['title']); $this->assertEquals($document['releaseYear'], $response['body']['releaseYear']); $this->assertArrayNotHasKey('birthDay', $response['body']); + $sequence = $response['body']['$sequence']; - // Query by sequence + // Query by sequence on get single document route $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $document['$collectionId'] . '/documents/' . $document['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::equal('$sequence', [$sequence]) + Query::equal('$sequence', [$sequence])->toString() + ], + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid query method: equal', $response['body']['message']); + + // Query by sequence + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $document['$collectionId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('$sequence', [$sequence.''])->toString() ], ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals($document['title'], $response['body']['title']); - $this->assertEquals($document['releaseYear'], $response['body']['releaseYear']); - $this->assertTrue(array_key_exists('$sequence', $response['body'])); + $this->assertEquals($document['title'], $response['body']['documents'][0]['title']); + $this->assertEquals($document['releaseYear'], $response['body']['documents'][0]['releaseYear']); + $this->assertTrue(array_key_exists('$sequence', $response['body']['documents'][0])); } /** diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index 7b2f903aa1..50f5cd4232 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -2358,6 +2358,36 @@ trait DatabasesBase $this->assertEquals($row['title'], $response['body']['title']); $this->assertEquals($row['releaseYear'], $response['body']['releaseYear']); $this->assertArrayNotHasKey('birthDay', $response['body']); + + $sequence = $response['body']['$sequence']; + + // Query by sequence on get single row route + $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $row['$tableId'] . '/rows/' . $row['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('$sequence', [$sequence])->toString() + ], + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('Invalid query method: equal', $response['body']['message']); + + // Query by sequence + $response = $this->client->call(Client::METHOD_GET, '/tablesdb/' . $databaseId . '/tables/' . $row['$tableId'] . '/rows', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('$sequence', [$sequence.''])->toString() + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($row['title'], $response['body']['rows'][0]['title']); + $this->assertEquals($row['releaseYear'], $response['body']['rows'][0]['releaseYear']); + $this->assertTrue(array_key_exists('$sequence', $response['body']['rows'][0])); } /** diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index be0b89b404..5c62240891 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -546,7 +546,7 @@ class ProxyCustomServerTest extends Scope $rules = $this->listRules([ 'search' => $rule1Domain, - 'queries' => [ Query::orderDesc('$createdAt') ] + 'queries' => [ Query::orderDesc('$createdAt')->toString() ] ]); $this->assertEquals(200, $rules['headers']['status-code']); @@ -555,7 +555,7 @@ class ProxyCustomServerTest extends Scope $rules = $this->listRules([ 'search' => $rule2Domain, - 'queries' => [ Query::orderDesc('$createdAt') ] + 'queries' => [ Query::orderDesc('$createdAt')->toString() ] ]); $this->assertEquals(200, $rules['headers']['status-code']); $ruleIds = \array_column($rules['body']['rules'], '$id'); @@ -563,7 +563,7 @@ class ProxyCustomServerTest extends Scope $rules = $this->listRules([ 'search' => $rule1Id, - 'queries' => [ Query::orderDesc('$createdAt') ] + 'queries' => [ Query::orderDesc('$createdAt')->toString() ] ]); $this->assertEquals(200, $rules['headers']['status-code']); $ruleDomains = \array_column($rules['body']['rules'], 'domain'); @@ -571,7 +571,7 @@ class ProxyCustomServerTest extends Scope $rules = $this->listRules([ 'search' => $rule2Id, - 'queries' => [ Query::orderDesc('$createdAt') ] + 'queries' => [ Query::orderDesc('$createdAt')->toString() ] ]); $this->assertEquals(200, $rules['headers']['status-code']); $ruleDomains = \array_column($rules['body']['rules'], 'domain'); diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index ab0a6946d6..b099342dce 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -155,7 +155,7 @@ class SitesCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'queries' => [ - Query::equal('deploymentResourceId', [$siteId]) + Query::equal('deploymentResourceId', [$siteId])->toString() ] ]);