From 280369340224b46ceb758b77e2391e21dfbc258b Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 21 May 2025 13:43:10 +0000 Subject: [PATCH 01/15] chore: sync with main --- .github/workflows/sdk-preview.yml | 2 +- composer.json | 2 +- composer.lock | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/sdk-preview.yml b/.github/workflows/sdk-preview.yml index e317845768..8abe2c2f91 100644 --- a/.github/workflows/sdk-preview.yml +++ b/.github/workflows/sdk-preview.yml @@ -47,7 +47,7 @@ jobs: node-version: 20 - name: Build and Publish SDK - working-directory: ./app/sdks/console-web + working-directory: ./app/sdks/${{ steps.set-sdk.outputs.platform }}-${{ steps.set-sdk.outputs.sdk_type }} run: | npm install npm run build diff --git a/composer.json b/composer.json index 7e445cd36b..20b0317b3e 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,7 @@ }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "0.40.*", + "appwrite/sdk-generator": "dev-fix-devkeys-header", "phpunit/phpunit": "9.*", "swoole/ide-helper": "5.1.2", "phpstan/phpstan": "1.8.*", diff --git a/composer.lock b/composer.lock index f36b815777..d762ace581 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": "9f5de64d73e2ef73d796fa64f2baf232", + "content-hash": "d4762811abd986e47a4d1259cfb1a0de", "packages": [ { "name": "adhocore/jwt", @@ -4816,16 +4816,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.17", + "version": "dev-fix-devkeys-header", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "7e333c1003bfd4763e4d6f3a0a799fde5e7bc4de" + "reference": "6a31bbe97dbfa972960fbc4d9ed51dfcee6deb07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/7e333c1003bfd4763e4d6f3a0a799fde5e7bc4de", - "reference": "7e333c1003bfd4763e4d6f3a0a799fde5e7bc4de", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6a31bbe97dbfa972960fbc4d9ed51dfcee6deb07", + "reference": "6a31bbe97dbfa972960fbc4d9ed51dfcee6deb07", "shasum": "" }, "require": { @@ -4861,9 +4861,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/0.40.17" + "source": "https://github.com/appwrite/sdk-generator/tree/fix-devkeys-header" }, - "time": "2025-05-16T15:10:54+00:00" + "time": "2025-05-21T13:05:09+00:00" }, { "name": "doctrine/annotations", @@ -8242,7 +8242,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "appwrite/sdk-generator": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8266,5 +8268,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From c00d032c5a3fc6490bf0378eac3b313b9b77f419 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 21 May 2025 14:45:09 +0000 Subject: [PATCH 02/15] chore: update platforms --- app/config/platforms.php | 4 ++-- composer.json | 2 +- composer.lock | 18 ++++++++---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 358edd0cf8..4ebb049633 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '18.1.0', + 'version' => '18.1.1', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -134,7 +134,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.9.1', + 'version' => '0.9.2', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, diff --git a/composer.json b/composer.json index 20b0317b3e..7e445cd36b 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,7 @@ }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "dev-fix-devkeys-header", + "appwrite/sdk-generator": "0.40.*", "phpunit/phpunit": "9.*", "swoole/ide-helper": "5.1.2", "phpstan/phpstan": "1.8.*", diff --git a/composer.lock b/composer.lock index d762ace581..9ba4927742 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": "d4762811abd986e47a4d1259cfb1a0de", + "content-hash": "9f5de64d73e2ef73d796fa64f2baf232", "packages": [ { "name": "adhocore/jwt", @@ -4816,16 +4816,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "dev-fix-devkeys-header", + "version": "0.40.18", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "6a31bbe97dbfa972960fbc4d9ed51dfcee6deb07" + "reference": "38de4b9c58112d7e83eb75955994c8412a401093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6a31bbe97dbfa972960fbc4d9ed51dfcee6deb07", - "reference": "6a31bbe97dbfa972960fbc4d9ed51dfcee6deb07", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/38de4b9c58112d7e83eb75955994c8412a401093", + "reference": "38de4b9c58112d7e83eb75955994c8412a401093", "shasum": "" }, "require": { @@ -4861,9 +4861,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/fix-devkeys-header" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.18" }, - "time": "2025-05-21T13:05:09+00:00" + "time": "2025-05-21T14:14:47+00:00" }, { "name": "doctrine/annotations", @@ -8242,9 +8242,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "appwrite/sdk-generator": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { From 24e0b3e8cb5fdf9ee6ea1e258c6431f0adc4c509 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 25 May 2025 12:19:52 +0530 Subject: [PATCH 03/15] fix: missing project check. --- app/controllers/api/teams.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 9c383803f2..cc930eca90 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1314,10 +1314,11 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') )) ->param('teamId', '', new UID(), 'Team ID.') ->param('membershipId', '', new UID(), 'Membership ID.') + ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $teamId, string $membershipId, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) { $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1341,6 +1342,27 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH); } + if ($project->getId() === 'console') { + // Quick check: + // fetch up to 2 owners to determine if only one exists + $ownersCount = $dbForProject->count( + collection: 'memberships', + queries: [ + Query::contains('roles', ['owner']), + Query::equal('teamInternalId', [$team->getInternalId()]) + ], + max: 2 + ); + + if ($ownersCount === 1) { + /* Prevent removal if the user is the only owner. */ + throw new Exception( + Exception::GENERAL_ARGUMENT_INVALID, + 'There must be at least one owner in the organization.' + ); + } + } + try { $dbForProject->deleteDocument('memberships', $membership->getId()); } catch (AuthorizationException $exception) { From 3f839e380788e901c20336784e01c3183dc33ea2 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 25 May 2025 12:27:29 +0530 Subject: [PATCH 04/15] update: tests to account for console and custom client projects. --- tests/e2e/Services/Teams/TeamsBaseClient.php | 79 ++++++++++++++++--- .../Services/Teams/TeamsConsoleClientTest.php | 4 +- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 3fcd9c043d..2aacf4abc1 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -10,6 +10,17 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator; trait TeamsBaseClient { + /** + * Checks if the current project is the Console project. + * + * This is necessary because the last admin of an org + * cannot be removed, which affects certain test conditions. + */ + private function isConsoleProject(): bool + { + return $this->getProject()['$id'] === 'console'; + } + /** * @depends testCreateTeam */ @@ -60,7 +71,11 @@ trait TeamsBaseClient ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['memberships']); + if ($this->isConsoleProject()) { + $this->assertCount(1, $response['body']['memberships']); + } else { + $this->assertCount(0, $response['body']['memberships']); + } $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -84,7 +99,11 @@ trait TeamsBaseClient ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertCount(0, $response['body']['memberships']); + if ($this->isConsoleProject()) { + $this->assertCount(1, $response['body']['memberships']); + } else { + $this->assertCount(0, $response['body']['memberships']); + } $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -373,7 +392,11 @@ trait TeamsBaseClient $this->assertEquals(200, $memberships['headers']['status-code']); $this->assertIsInt($memberships['body']['total']); $this->assertNotEmpty($memberships['body']['memberships']); - $this->assertCount(3, $memberships['body']['memberships']); + if ($this->isConsoleProject()) { + $this->assertCount(4, $memberships['body']['memberships']); + } else { + $this->assertCount(3, $memberships['body']['memberships']); + } $response = $this->client->call(Client::METHOD_GET, '/teams/' . $data['teamUid'] . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -387,7 +410,11 @@ trait TeamsBaseClient $this->assertEquals(200, $response['headers']['status-code']); $this->assertIsInt($response['body']['total']); $this->assertNotEmpty($response['body']['memberships']); - $this->assertCount(2, $response['body']['memberships']); + if ($this->isConsoleProject()) { + $this->assertCount(3, $response['body']['memberships']); + } else { + $this->assertCount(2, $response['body']['memberships']); + } $this->assertEquals($memberships['body']['memberships'][1]['$id'], $response['body']['memberships'][0]['$id']); } @@ -724,7 +751,11 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(4, $response['body']['total']); + if ($this->isConsoleProject()) { + $this->assertCount(5, $response['body']['memberships']); + } else { + $this->assertCount(4, $response['body']['memberships']); + } $ownerMembershipUid = $response['body']['memberships'][0]['$id']; @@ -754,14 +785,18 @@ trait TeamsBaseClient 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]); - $this->assertEquals(401, $response['headers']['status-code']); + if ($this->isConsoleProject()) { + $this->assertEquals(400, $response['headers']['status-code']); + } else { + $this->assertEquals(401, $response['headers']['status-code']); + } /** * Test for SUCCESS */ /** - * Test for when a user other than the owner tries to delete their membership + * Test for when a user other than the owner tries to delete an owner's membership. */ $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [ 'origin' => 'http://localhost', @@ -770,8 +805,13 @@ trait TeamsBaseClient 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]); - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEmpty($response['body']); + if ($this->isConsoleProject()) { + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + } else { + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEmpty($response['body']); + } $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -779,7 +819,11 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(3, $response['body']['total']); + if ($this->isConsoleProject()) { + $this->assertEquals(5, $response['body']['total']); + } else { + $this->assertEquals(3, $response['body']['total']); + } /** * Test for when the owner tries to delete their membership @@ -790,8 +834,13 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEmpty($response['body']); + if ($this->isConsoleProject()) { + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + } else { + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEmpty($response['body']); + } $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, array_merge([ 'origin' => 'http://localhost', @@ -799,7 +848,11 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(404, $response['headers']['status-code']); + if ($this->isConsoleProject()) { + $this->assertEquals(200, $response['headers']['status-code']); + } else { + $this->assertEquals(404, $response['headers']['status-code']); + } return []; } diff --git a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php index 06358765f6..3798019884 100644 --- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php +++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php @@ -73,7 +73,9 @@ class TeamsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - $this->assertEquals(204, $response['headers']['status-code']); + + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); return $data; } From 9bb52cda93a8123380fefde69ac8a4001fac08f8 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 26 May 2025 04:32:10 +0000 Subject: [PATCH 05/15] chore: update flutter sdk and changelog --- app/config/platforms.php | 2 +- composer.lock | 74 +++++++++++++++++----------------- docs/sdks/flutter/CHANGELOG.md | 4 +- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/app/config/platforms.php b/app/config/platforms.php index 4ebb049633..4e705516fa 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -59,7 +59,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '16.1.0', + 'version' => '17.0.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, diff --git a/composer.lock b/composer.lock index 9ba4927742..875f012fc9 100644 --- a/composer.lock +++ b/composer.lock @@ -1238,16 +1238,16 @@ }, { "name": "open-telemetry/exporter-otlp", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/exporter-otlp.git", - "reference": "19adf03d2b0f91f9e9b1c7f93db6c755c737cf6c" + "reference": "8b3ca1f86d01429c73b407bf1a2075d9c187001e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/19adf03d2b0f91f9e9b1c7f93db6c755c737cf6c", - "reference": "19adf03d2b0f91f9e9b1c7f93db6c755c737cf6c", + "url": "https://api.github.com/repos/opentelemetry-php/exporter-otlp/zipball/8b3ca1f86d01429c73b407bf1a2075d9c187001e", + "reference": "8b3ca1f86d01429c73b407bf1a2075d9c187001e", "shasum": "" }, "require": { @@ -1298,7 +1298,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-12T00:36:35+00:00" + "time": "2025-05-21T12:02:20+00:00" }, { "name": "open-telemetry/gen-otlp-protobuf", @@ -1365,16 +1365,16 @@ }, { "name": "open-telemetry/sdk", - "version": "1.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/opentelemetry-php/sdk.git", - "reference": "939d3a28395c249a763676458140dad44b3a8011" + "reference": "cd0d7367599717fc29e04eb8838ec061e6c2c657" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/939d3a28395c249a763676458140dad44b3a8011", - "reference": "939d3a28395c249a763676458140dad44b3a8011", + "url": "https://api.github.com/repos/opentelemetry-php/sdk/zipball/cd0d7367599717fc29e04eb8838ec061e6c2c657", + "reference": "cd0d7367599717fc29e04eb8838ec061e6c2c657", "shasum": "" }, "require": { @@ -1451,7 +1451,7 @@ "issues": "https://github.com/open-telemetry/opentelemetry-php/issues", "source": "https://github.com/open-telemetry/opentelemetry-php" }, - "time": "2025-05-07T12:32:21+00:00" + "time": "2025-05-22T02:33:34+00:00" }, { "name": "open-telemetry/sem-conv", @@ -2486,16 +2486,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -2508,7 +2508,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -2533,7 +2533,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -2549,7 +2549,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/http-client", @@ -2648,16 +2648,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.5.2", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + "reference": "75d7043853a42837e68111812f4d964b01e5101c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", - "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", "shasum": "" }, "require": { @@ -2670,7 +2670,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -2706,7 +2706,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" }, "funding": [ { @@ -2722,7 +2722,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:49:48+00:00" + "time": "2025-04-29T11:18:49+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -2883,16 +2883,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -2910,7 +2910,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -2946,7 +2946,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -2962,7 +2962,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { "name": "tbachert/spi", @@ -4816,16 +4816,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.18", + "version": "0.40.19", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "38de4b9c58112d7e83eb75955994c8412a401093" + "reference": "05b53cf30c59fe5934d57207fefbc8ae8feb5dbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/38de4b9c58112d7e83eb75955994c8412a401093", - "reference": "38de4b9c58112d7e83eb75955994c8412a401093", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/05b53cf30c59fe5934d57207fefbc8ae8feb5dbf", + "reference": "05b53cf30c59fe5934d57207fefbc8ae8feb5dbf", "shasum": "" }, "require": { @@ -4861,9 +4861,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/0.40.18" + "source": "https://github.com/appwrite/sdk-generator/tree/0.40.19" }, - "time": "2025-05-21T14:14:47+00:00" + "time": "2025-05-24T22:49:50+00:00" }, { "name": "doctrine/annotations", diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index f707c645e6..1a9b8c2e29 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,9 +1,11 @@ # Change Log -## 16.1.1 +## 17.0.0 * Update `flutter_web_auth_2` dependency to version 4.1.0 * Update `auth.html` example in README.md to align with `flutter_web_auth_2` documentation +* Breaking changes: + * Minimum iOS version supported is now 17.4 due to the updated requirements of `flutter_web_auth_2` version 4.1.0 ## 16.1.0 From 856a9a714b4923f79cffe7b235bf842829cb167c Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 May 2025 11:29:46 +0530 Subject: [PATCH 06/15] fix: memberships patch and delete logic. --- app/controllers/api/teams.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index cc930eca90..7e0d440bd3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1096,9 +1096,12 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') max: 2 ); + // Is the role change being requested by the user on their own membership? + $isCurrentUserAnOwner = $user->getInternalId() === $membership->getAttribute('userInternalId'); + // Prevent role change if there's only one owner left, - // the requester is that owner, and the new `$roles` no longer include 'owner'! - if ($ownersCount === 1 && $isOwner && !\in_array('owner', $roles)) { + // the requester is that owner, and the new `$roles` no longer include 'owner' + if ($ownersCount === 1 && $isOwner && $isCurrentUserAnOwner && !\in_array('owner', $roles)) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.'); } } @@ -1314,11 +1317,12 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') )) ->param('teamId', '', new UID(), 'Team ID.') ->param('membershipId', '', new UID(), 'Membership ID.') + ->inject('user') ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $teamId, string $membershipId, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) { $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -1326,9 +1330,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::TEAM_INVITE_NOT_FOUND); } - $user = $dbForProject->getDocument('users', $membership->getAttribute('userId')); + $profile = $dbForProject->getDocument('users', $membership->getAttribute('userId')); - if ($user->isEmpty()) { + if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } @@ -1354,7 +1358,10 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') max: 2 ); - if ($ownersCount === 1) { + // Is the deletion being requested by the user on their own membership? + $isCurrentUserAnOwner = $user->getInternalId() === $membership->getAttribute('userInternalId'); + + if ($ownersCount === 1 && $isCurrentUserAnOwner) { /* Prevent removal if the user is the only owner. */ throw new Exception( Exception::GENERAL_ARGUMENT_INVALID, @@ -1378,8 +1385,8 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') } $queueForEvents - ->setParam('userId', $user->getId()) ->setParam('teamId', $team->getId()) + ->setParam('userId', $profile->getId()) ->setParam('membershipId', $membership->getId()) ->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP)) ; From 96bef81ebc2ac062fbe3bd2bde8be5775fe2cf1e Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 May 2025 11:33:58 +0530 Subject: [PATCH 07/15] fix: tests! --- tests/e2e/Services/Teams/TeamsBaseClient.php | 83 +++--------- .../Services/Teams/TeamsConsoleClientTest.php | 125 +++++++++++++++--- 2 files changed, 119 insertions(+), 89 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 2aacf4abc1..9b19572d7b 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -10,17 +10,6 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator; trait TeamsBaseClient { - /** - * Checks if the current project is the Console project. - * - * This is necessary because the last admin of an org - * cannot be removed, which affects certain test conditions. - */ - private function isConsoleProject(): bool - { - return $this->getProject()['$id'] === 'console'; - } - /** * @depends testCreateTeam */ @@ -71,11 +60,7 @@ trait TeamsBaseClient ]); $this->assertEquals(200, $response['headers']['status-code']); - if ($this->isConsoleProject()) { - $this->assertCount(1, $response['body']['memberships']); - } else { - $this->assertCount(0, $response['body']['memberships']); - } + $this->assertCount(0, $response['body']['memberships']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -99,11 +84,7 @@ trait TeamsBaseClient ]); $this->assertEquals(200, $response['headers']['status-code']); - if ($this->isConsoleProject()) { - $this->assertCount(1, $response['body']['memberships']); - } else { - $this->assertCount(0, $response['body']['memberships']); - } + $this->assertCount(0, $response['body']['memberships']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -392,11 +373,7 @@ trait TeamsBaseClient $this->assertEquals(200, $memberships['headers']['status-code']); $this->assertIsInt($memberships['body']['total']); $this->assertNotEmpty($memberships['body']['memberships']); - if ($this->isConsoleProject()) { - $this->assertCount(4, $memberships['body']['memberships']); - } else { - $this->assertCount(3, $memberships['body']['memberships']); - } + $this->assertCount(3, $memberships['body']['memberships']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $data['teamUid'] . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -410,11 +387,7 @@ trait TeamsBaseClient $this->assertEquals(200, $response['headers']['status-code']); $this->assertIsInt($response['body']['total']); $this->assertNotEmpty($response['body']['memberships']); - if ($this->isConsoleProject()) { - $this->assertCount(3, $response['body']['memberships']); - } else { - $this->assertCount(2, $response['body']['memberships']); - } + $this->assertCount(2, $response['body']['memberships']); $this->assertEquals($memberships['body']['memberships'][1]['$id'], $response['body']['memberships'][0]['$id']); } @@ -737,8 +710,8 @@ trait TeamsBaseClient } /** - * @depends testUpdateTeamMembershipRoles - */ + * @depends testUpdateTeamMembershipRoles + */ public function testDeleteTeamMembership($data): array { $teamUid = $data['teamUid'] ?? ''; @@ -751,11 +724,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - if ($this->isConsoleProject()) { - $this->assertCount(5, $response['body']['memberships']); - } else { - $this->assertCount(4, $response['body']['memberships']); - } + $this->assertEquals(4, $response['body']['total']); $ownerMembershipUid = $response['body']['memberships'][0]['$id']; @@ -785,18 +754,14 @@ trait TeamsBaseClient 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]); - if ($this->isConsoleProject()) { - $this->assertEquals(400, $response['headers']['status-code']); - } else { - $this->assertEquals(401, $response['headers']['status-code']); - } + $this->assertEquals(401, $response['headers']['status-code']); /** * Test for SUCCESS */ /** - * Test for when a user other than the owner tries to delete an owner's membership. + * Test for when a user other than the owner tries to delete their membership */ $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [ 'origin' => 'http://localhost', @@ -805,13 +770,8 @@ trait TeamsBaseClient 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]); - if ($this->isConsoleProject()) { - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - } else { - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEmpty($response['body']); - } + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEmpty($response['body']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', @@ -819,11 +779,7 @@ trait TeamsBaseClient ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); - if ($this->isConsoleProject()) { - $this->assertEquals(5, $response['body']['total']); - } else { - $this->assertEquals(3, $response['body']['total']); - } + $this->assertEquals(3, $response['body']['total']); /** * Test for when the owner tries to delete their membership @@ -834,13 +790,8 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - if ($this->isConsoleProject()) { - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']); - } else { - $this->assertEquals(204, $response['headers']['status-code']); - $this->assertEmpty($response['body']); - } + $this->assertEquals(204, $response['headers']['status-code']); + $this->assertEmpty($response['body']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, array_merge([ 'origin' => 'http://localhost', @@ -848,11 +799,7 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - if ($this->isConsoleProject()) { - $this->assertEquals(200, $response['headers']['status-code']); - } else { - $this->assertEquals(404, $response['headers']['status-code']); - } + $this->assertEquals(404, $response['headers']['status-code']); return []; } diff --git a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php index 3798019884..f414123788 100644 --- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php +++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php @@ -41,7 +41,7 @@ class TeamsConsoleClientTest extends Scope /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([ + $developer = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -51,7 +51,8 @@ class TeamsConsoleClientTest extends Scope 'url' => 'http://localhost:5000/join-us#title' ]); - $this->assertEquals(201, $response['headers']['status-code']); + $developerUserId = $developer['body']['$id']; + $this->assertEquals(201, $developer['headers']['status-code']); $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ 'content-type' => 'application/json', @@ -66,7 +67,7 @@ class TeamsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $ownerMembershipUid = $response['body']['memberships'][1]['$id']; + $ownerMembershipUid = $response['body']['memberships'][0]['$id']; $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, array_merge([ 'origin' => 'http://localhost', @@ -77,6 +78,16 @@ class TeamsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); + // Remove the excess developer member to reduce the membership count in `TeamsBaseClient` tests. + // This is necessary because the only owner cannot be removed in the console project / top level team / organization. + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $developerUserId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + return $data; } @@ -87,21 +98,6 @@ class TeamsConsoleClientTest extends Scope $membershipUid = $data['membershipUid'] ?? ''; $session = $data['session'] ?? ''; - /** - * Test for FAILURE - */ - $roles = ['developer']; - $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid, array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'roles' => $roles - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); - /** * Test for unknown team */ @@ -110,7 +106,7 @@ class TeamsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'roles' => $roles + 'roles' => ['developer'] ]); $this->assertEquals(404, $response['headers']['status-code']); @@ -123,7 +119,7 @@ class TeamsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'roles' => $roles + 'roles' => ['developer'] ]); $this->assertEquals(404, $response['headers']['status-code']); @@ -138,7 +134,7 @@ class TeamsConsoleClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ], [ - 'roles' => $roles + 'roles' => ['developer'] ]); $this->assertEquals(401, $response['headers']['status-code']); @@ -146,4 +142,91 @@ class TeamsConsoleClientTest extends Scope return $data; } + + /** + * @depends testUpdateTeamMembershipRoles + */ + public function testDeleteTeamMembership($data): array + { + $teamUid = $data['teamUid'] ?? ''; + $membershipUid = $data['membershipUid'] ?? ''; + $session = $data['session'] ?? ''; + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['total']); + + $ownerMembershipUid = $response['body']['memberships'][0]['$id']; + + /** + * Test deleting a membership that does not exists + */ + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/dne', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + /** + * Test deleting another user's membership + */ + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + $this->assertEquals('The current user is not authorized to perform the requested action.', $response['body']['message']); + + /** + * Test for when a user other than the owner tries to delete their membership + */ + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $membershipUid, [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(3, $response['body']['total']); + + /** + * Test for when the owner tries to delete their membership + */ + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + + return []; + } } From 64baf1071d5bd7137c0bd159a64c630c53dd8cff Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 May 2025 11:38:22 +0530 Subject: [PATCH 08/15] fix: wrong variable passed to cache purge. --- app/controllers/api/teams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 7e0d440bd3..5cac3dc9ea 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1378,7 +1378,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB'); } - $dbForProject->purgeCachedDocument('users', $user->getId()); + $dbForProject->purgeCachedDocument('users', $profile->getId()); if ($membership->getAttribute('confirm')) { // Count only confirmed members Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0)); From 3adbe8a8f51030947ed09604ac9be30e4d3ebb67 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 May 2025 11:41:58 +0530 Subject: [PATCH 09/15] revert: minor change. --- tests/e2e/Services/Teams/TeamsBaseClient.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 9b19572d7b..3fcd9c043d 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -710,8 +710,8 @@ trait TeamsBaseClient } /** - * @depends testUpdateTeamMembershipRoles - */ + * @depends testUpdateTeamMembershipRoles + */ public function testDeleteTeamMembership($data): array { $teamUid = $data['teamUid'] ?? ''; From 7e6f4befa58dcf83d86fc976eeeb7eec2e8bf170 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 May 2025 19:21:28 +0530 Subject: [PATCH 10/15] reset: stuff from main. --- app/cli.php | 3 +- app/controllers/api/storage.php | 60 +++++++++---------- src/Appwrite/Platform/Tasks/ScheduleBase.php | 10 ++-- .../Platform/Tasks/ScheduleFunctions.php | 2 +- .../Platform/Tasks/ScheduleMessages.php | 2 +- 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/app/cli.php b/app/cli.php index 7c2daf4500..9517347420 100644 --- a/app/cli.php +++ b/app/cli.php @@ -284,5 +284,6 @@ $cli $cli->shutdown()->action(fn () => Timer::clearAll()); -Runtime::enableCoroutine(SWOOLE_HOOK_ALL); +// Enable coroutines, but disable TCP hooks. These don't work until we use `\Utopia\Cache\Adapter\Pool` and `\Utopia\Database\Adapter\Pool`. +Runtime::enableCoroutine(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_TCP); run($cli->run(...)); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index d2edf476f6..b3b8fb906a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1004,7 +1004,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $algorithm = $file->getAttribute('algorithm', Compression::NONE); $cipher = $file->getAttribute('openSSLCipher'); $mime = $file->getAttribute('mimeType'); - if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) System::getEnv('_APP_STORAGE_PREVIEW_LIMIT', APP_STORAGE_READ_BUFFER)) { + if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) System::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) { if (!\in_array($mime, $inputs)) { $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; } else { @@ -1178,6 +1178,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path); } + $response + ->setContentType($file->getAttribute('mimeType')) + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('X-Peak', \memory_get_peak_usage()) + ->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"') + ; + $size = $file->getAttribute('sizeOriginal', 0); $rangeHeader = $request->getHeader('range'); @@ -1186,7 +1193,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') $end = $request->getRangeEnd(); $unit = $request->getRangeUnit(); - if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) { + if ($end === null) { $end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1)); } @@ -1201,13 +1208,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); } - $response - ->setContentType($file->getAttribute('mimeType')) - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days - ->addHeader('X-Peak', \memory_get_peak_usage()) - ->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"') - ; - $source = ''; if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = $deviceForFiles->read($path); @@ -1345,6 +1345,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') $contentType = $file->getAttribute('mimeType'); } + $response + ->setContentType($contentType) + ->addHeader('Content-Security-Policy', 'script-src none;') + ->addHeader('X-Content-Type-Options', 'nosniff') + ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('X-Peak', \memory_get_peak_usage()) + ; + $size = $file->getAttribute('sizeOriginal', 0); $rangeHeader = $request->getHeader('range'); @@ -1353,8 +1362,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') $end = $request->getRangeEnd(); $unit = $request->getRangeUnit(); - if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) { - $end = min(($start + APP_STORAGE_READ_BUFFER - 1), ($size - 1)); + if ($end === null) { + $end = min(($start + 2000000 - 1), ($size - 1)); } if ($unit != 'bytes' || $start >= $end || $end >= $size) { @@ -1368,15 +1377,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); } - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days - ->addHeader('X-Peak', \memory_get_peak_usage()) - ; - $source = ''; if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = $deviceForFiles->read($path); @@ -1498,6 +1498,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') $contentType = $file->getAttribute('mimeType'); } + $response + ->setContentType($contentType) + ->addHeader('Content-Security-Policy', 'script-src none;') + ->addHeader('X-Content-Type-Options', 'nosniff') + ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('X-Peak', \memory_get_peak_usage()); + $size = $file->getAttribute('sizeOriginal', 0); $rangeHeader = $request->getHeader('range'); @@ -1506,8 +1514,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') $end = $request->getRangeEnd(); $unit = $request->getRangeUnit(); - if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) { - $end = min(($start + APP_STORAGE_READ_BUFFER - 1), ($size - 1)); + if ($end === null) { + $end = min(($start + 2000000 - 1), ($size - 1)); } if ($unit != 'bytes' || $start >= $end || $end >= $size) { @@ -1521,14 +1529,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); } - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days - ->addHeader('X-Peak', \memory_get_peak_usage()); - $source = ''; if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = $deviceForFiles->read($path); diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 28c77c1832..286ffe45cb 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -79,16 +79,16 @@ abstract class ScheduleBase extends Action // start with "0" to load all active documents. $lastSyncUpdate = "0"; - $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate); + $this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate); Console::success("Starting timers at " . DateTime::now()); /** * The timer synchronize $schedules copy with database collection. */ - Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, $getProjectDB, &$lastSyncUpdate) { + Timer::tick(static::UPDATE_TIMER * 1000, function () use ($pools, $dbForPlatform, $getProjectDB, &$lastSyncUpdate) { $time = DateTime::now(); Console::log("Sync tick: Running at $time"); - $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate); + $this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate); }); while (true) { @@ -103,7 +103,7 @@ abstract class ScheduleBase extends Action } } - private function collectSchedules(Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void + private function collectSchedules(Group $pools, Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void { // If we haven't synced yet, load all active schedules $initialLoad = $lastSyncUpdate === "0"; @@ -115,7 +115,7 @@ abstract class ScheduleBase extends Action * @throws Exception * @var Document $schedule */ - $getSchedule = function (Document $schedule) use ($dbForPlatform, $getProjectDB): array { + $getSchedule = function (Document $schedule) use ($pools, $dbForPlatform, $getProjectDB): array { $project = $dbForPlatform->getDocument('projects', $schedule->getAttribute('projectId')); $resource = $getProjectDB($project)->getDocument( diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 7812b27832..19e068107a 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -76,7 +76,7 @@ class ScheduleFunctions extends ScheduleBase } foreach ($delayedExecutions as $delay => $schedules) { - \go(function () use ($delay, $schedules, $dbForPlatform) { + \go(function () use ($delay, $schedules, $pools, $dbForPlatform) { \sleep($delay); // in seconds foreach ($schedules as $delayConfig) { diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index d23e3de575..5e65f7a8a6 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -40,7 +40,7 @@ class ScheduleMessages extends ScheduleBase continue; } - \go(function () use ($schedule, $scheduledAt, $dbForPlatform) { + \go(function () use ($schedule, $scheduledAt, $pools, $dbForPlatform) { $queueForMessaging = new Messaging($this->publisher); $this->updateProjectAccess($schedule['project'], $dbForPlatform); From 2ef2c59d187acebaea28974678355873e31d23c9 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 30 May 2025 14:03:51 +0530 Subject: [PATCH 11/15] update: exceptions and messages. --- app/config/errors.php | 10 ++++++++++ app/controllers/api/teams.php | 7 ++----- src/Appwrite/Extend/Exception.php | 2 ++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 6d9d29a8ea..8365e8c705 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -393,6 +393,16 @@ return [ 'description' => 'Membership is already confirmed.', 'code' => 409, ], + Exception::MEMBERSHIP_DELETION_PROHIBITED => [ + 'name' => Exception::MEMBERSHIP_DELETION_PROHIBITED, + 'description' => 'Membership deletion is prohibited.', + 'code' => 400, + ], + Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED => [ + 'name' => Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED, + 'description' => 'Membership role downgrade is prohibited.', + 'code' => 400, + ], /** Avatars */ Exception::AVATAR_SET_NOT_FOUND => [ diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index aa85852049..cc10b7cead 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1103,7 +1103,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') // Prevent role change if there's only one owner left, // the requester is that owner, and the new `$roles` no longer include 'owner' if ($ownersCount === 1 && $isOwner && $isCurrentUserAnOwner && !\in_array('owner', $roles)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.'); + throw new Exception(Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED, 'There must be at least one owner.'); } } @@ -1364,10 +1364,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') if ($ownersCount === 1 && $isCurrentUserAnOwner) { /* Prevent removal if the user is the only owner. */ - throw new Exception( - Exception::GENERAL_ARGUMENT_INVALID, - 'There must be at least one owner in the organization.' - ); + throw new Exception(Exception::MEMBERSHIP_DELETION_PROHIBITED, 'There must be at least one owner.'); } } diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 296434ed57..3af6d9962c 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -124,6 +124,8 @@ class Exception extends \Exception /** Membership */ public const MEMBERSHIP_NOT_FOUND = 'membership_not_found'; public const MEMBERSHIP_ALREADY_CONFIRMED = 'membership_already_confirmed'; + public const MEMBERSHIP_DELETION_PROHIBITED = 'membership_deletion_prohibited'; + public const MEMBERSHIP_DOWNGRADE_PROHIBITED = 'membership_downgrade_prohibited'; /** Avatars */ public const AVATAR_SET_NOT_FOUND = 'avatar_set_not_found'; From 65ae22d7cd6af437066320d210b44ad1defe0513 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 30 May 2025 14:08:25 +0530 Subject: [PATCH 12/15] update: messages because the check is only for console project > organizations. --- app/controllers/api/teams.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index cc10b7cead..ede61115e2 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1103,7 +1103,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') // Prevent role change if there's only one owner left, // the requester is that owner, and the new `$roles` no longer include 'owner' if ($ownersCount === 1 && $isOwner && $isCurrentUserAnOwner && !\in_array('owner', $roles)) { - throw new Exception(Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED, 'There must be at least one owner.'); + throw new Exception(Exception::MEMBERSHIP_DOWNGRADE_PROHIBITED, 'There must be at least one owner in the organization.'); } } @@ -1364,7 +1364,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') if ($ownersCount === 1 && $isCurrentUserAnOwner) { /* Prevent removal if the user is the only owner. */ - throw new Exception(Exception::MEMBERSHIP_DELETION_PROHIBITED, 'There must be at least one owner.'); + throw new Exception(Exception::MEMBERSHIP_DELETION_PROHIBITED, 'There must be at least one owner in the organization.'); } } From 3063ac899c9a1cd971e068db3f04020a358fe1b4 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 30 May 2025 14:21:11 +0530 Subject: [PATCH 13/15] update: tests. --- tests/e2e/Services/Teams/TeamsBase.php | 2 +- tests/e2e/Services/Teams/TeamsConsoleClientTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 80ac1621ee..fccfded1e1 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -64,7 +64,7 @@ trait TeamsBase // Step 4: Assert failure — cannot remove the only OWNER from a team $this->assertEquals(400, $response['headers']['status-code']); - $this->assertEquals('general_argument_invalid', $response['body']['type']); + $this->assertEquals('membership_downgrade_prohibited', $response['body']['type']); $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); } diff --git a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php index 59945b1191..dda7f8e4ae 100644 --- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php +++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php @@ -100,6 +100,7 @@ class TeamsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('membership_deletion_prohibited', $response['body']['type']); $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); // Remove the excess developer member to reduce the membership count in `TeamsBaseClient` tests. @@ -241,6 +242,7 @@ class TeamsConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals('membership_deletion_prohibited', $response['body']['type']); $this->assertEquals('There must be at least one owner in the organization.', $response['body']['message']); $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships/' . $ownerMembershipUid, array_merge([ From 4fc84bec7d6153a5dacf4cc1f8df8177988b6350 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 30 May 2025 14:33:37 +0530 Subject: [PATCH 14/15] reset: stuff on target branch. --- .github/workflows/sdk-preview.yml | 2 +- app/cli.php | 3 +- app/config/platforms.php | 6 +- app/controllers/api/storage.php | 60 +++++++++---------- docs/sdks/flutter/CHANGELOG.md | 4 +- src/Appwrite/Platform/Tasks/ScheduleBase.php | 10 ++-- .../Platform/Tasks/ScheduleFunctions.php | 2 +- 7 files changed, 42 insertions(+), 45 deletions(-) diff --git a/.github/workflows/sdk-preview.yml b/.github/workflows/sdk-preview.yml index 8abe2c2f91..e317845768 100644 --- a/.github/workflows/sdk-preview.yml +++ b/.github/workflows/sdk-preview.yml @@ -47,7 +47,7 @@ jobs: node-version: 20 - name: Build and Publish SDK - working-directory: ./app/sdks/${{ steps.set-sdk.outputs.platform }}-${{ steps.set-sdk.outputs.sdk_type }} + working-directory: ./app/sdks/console-web run: | npm install npm run build diff --git a/app/cli.php b/app/cli.php index 9517347420..7c2daf4500 100644 --- a/app/cli.php +++ b/app/cli.php @@ -284,6 +284,5 @@ $cli $cli->shutdown()->action(fn () => Timer::clearAll()); -// Enable coroutines, but disable TCP hooks. These don't work until we use `\Utopia\Cache\Adapter\Pool` and `\Utopia\Database\Adapter\Pool`. -Runtime::enableCoroutine(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_TCP); +Runtime::enableCoroutine(SWOOLE_HOOK_ALL); run($cli->run(...)); diff --git a/app/config/platforms.php b/app/config/platforms.php index 4b2faf0487..7d481d508e 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '18.1.1', + 'version' => '18.1.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -59,7 +59,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '17.0.0', + 'version' => '16.1.0', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -134,7 +134,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.9.2', + 'version' => '0.9.1', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index b3b8fb906a..d2edf476f6 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1004,7 +1004,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $algorithm = $file->getAttribute('algorithm', Compression::NONE); $cipher = $file->getAttribute('openSSLCipher'); $mime = $file->getAttribute('mimeType'); - if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) System::getEnv('_APP_STORAGE_PREVIEW_LIMIT', 20000000)) { + if (!\in_array($mime, $inputs) || $file->getAttribute('sizeActual') > (int) System::getEnv('_APP_STORAGE_PREVIEW_LIMIT', APP_STORAGE_READ_BUFFER)) { if (!\in_array($mime, $inputs)) { $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; } else { @@ -1178,13 +1178,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path); } - $response - ->setContentType($file->getAttribute('mimeType')) - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days - ->addHeader('X-Peak', \memory_get_peak_usage()) - ->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"') - ; - $size = $file->getAttribute('sizeOriginal', 0); $rangeHeader = $request->getHeader('range'); @@ -1193,7 +1186,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') $end = $request->getRangeEnd(); $unit = $request->getRangeUnit(); - if ($end === null) { + if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) { $end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1)); } @@ -1208,6 +1201,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); } + $response + ->setContentType($file->getAttribute('mimeType')) + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('X-Peak', \memory_get_peak_usage()) + ->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"') + ; + $source = ''; if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = $deviceForFiles->read($path); @@ -1345,15 +1345,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') $contentType = $file->getAttribute('mimeType'); } - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days - ->addHeader('X-Peak', \memory_get_peak_usage()) - ; - $size = $file->getAttribute('sizeOriginal', 0); $rangeHeader = $request->getHeader('range'); @@ -1362,8 +1353,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') $end = $request->getRangeEnd(); $unit = $request->getRangeUnit(); - if ($end === null) { - $end = min(($start + 2000000 - 1), ($size - 1)); + if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) { + $end = min(($start + APP_STORAGE_READ_BUFFER - 1), ($size - 1)); } if ($unit != 'bytes' || $start >= $end || $end >= $size) { @@ -1377,6 +1368,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); } + $response + ->setContentType($contentType) + ->addHeader('Content-Security-Policy', 'script-src none;') + ->addHeader('X-Content-Type-Options', 'nosniff') + ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('X-Peak', \memory_get_peak_usage()) + ; + $source = ''; if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = $deviceForFiles->read($path); @@ -1498,14 +1498,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') $contentType = $file->getAttribute('mimeType'); } - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days - ->addHeader('X-Peak', \memory_get_peak_usage()); - $size = $file->getAttribute('sizeOriginal', 0); $rangeHeader = $request->getHeader('range'); @@ -1514,8 +1506,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') $end = $request->getRangeEnd(); $unit = $request->getRangeUnit(); - if ($end === null) { - $end = min(($start + 2000000 - 1), ($size - 1)); + if ($end === null || $end - $start > APP_STORAGE_READ_BUFFER) { + $end = min(($start + APP_STORAGE_READ_BUFFER - 1), ($size - 1)); } if ($unit != 'bytes' || $start >= $end || $end >= $size) { @@ -1529,6 +1521,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') ->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT); } + $response + ->setContentType($contentType) + ->addHeader('Content-Security-Policy', 'script-src none;') + ->addHeader('X-Content-Type-Options', 'nosniff') + ->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"') + ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('X-Peak', \memory_get_peak_usage()); + $source = ''; if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt $source = $deviceForFiles->read($path); diff --git a/docs/sdks/flutter/CHANGELOG.md b/docs/sdks/flutter/CHANGELOG.md index 1a9b8c2e29..f707c645e6 100644 --- a/docs/sdks/flutter/CHANGELOG.md +++ b/docs/sdks/flutter/CHANGELOG.md @@ -1,11 +1,9 @@ # Change Log -## 17.0.0 +## 16.1.1 * Update `flutter_web_auth_2` dependency to version 4.1.0 * Update `auth.html` example in README.md to align with `flutter_web_auth_2` documentation -* Breaking changes: - * Minimum iOS version supported is now 17.4 due to the updated requirements of `flutter_web_auth_2` version 4.1.0 ## 16.1.0 diff --git a/src/Appwrite/Platform/Tasks/ScheduleBase.php b/src/Appwrite/Platform/Tasks/ScheduleBase.php index 286ffe45cb..28c77c1832 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleBase.php +++ b/src/Appwrite/Platform/Tasks/ScheduleBase.php @@ -79,16 +79,16 @@ abstract class ScheduleBase extends Action // start with "0" to load all active documents. $lastSyncUpdate = "0"; - $this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate); + $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate); Console::success("Starting timers at " . DateTime::now()); /** * The timer synchronize $schedules copy with database collection. */ - Timer::tick(static::UPDATE_TIMER * 1000, function () use ($pools, $dbForPlatform, $getProjectDB, &$lastSyncUpdate) { + Timer::tick(static::UPDATE_TIMER * 1000, function () use ($dbForPlatform, $getProjectDB, &$lastSyncUpdate) { $time = DateTime::now(); Console::log("Sync tick: Running at $time"); - $this->collectSchedules($pools, $dbForPlatform, $getProjectDB, $lastSyncUpdate); + $this->collectSchedules($dbForPlatform, $getProjectDB, $lastSyncUpdate); }); while (true) { @@ -103,7 +103,7 @@ abstract class ScheduleBase extends Action } } - private function collectSchedules(Group $pools, Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void + private function collectSchedules(Database $dbForPlatform, callable $getProjectDB, string &$lastSyncUpdate): void { // If we haven't synced yet, load all active schedules $initialLoad = $lastSyncUpdate === "0"; @@ -115,7 +115,7 @@ abstract class ScheduleBase extends Action * @throws Exception * @var Document $schedule */ - $getSchedule = function (Document $schedule) use ($pools, $dbForPlatform, $getProjectDB): array { + $getSchedule = function (Document $schedule) use ($dbForPlatform, $getProjectDB): array { $project = $dbForPlatform->getDocument('projects', $schedule->getAttribute('projectId')); $resource = $getProjectDB($project)->getDocument( diff --git a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php index 19e068107a..7812b27832 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleFunctions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleFunctions.php @@ -76,7 +76,7 @@ class ScheduleFunctions extends ScheduleBase } foreach ($delayedExecutions as $delay => $schedules) { - \go(function () use ($delay, $schedules, $pools, $dbForPlatform) { + \go(function () use ($delay, $schedules, $dbForPlatform) { \sleep($delay); // in seconds foreach ($schedules as $delayConfig) { From 36bc41a748a03f3820d514d9064edd4cc1b4bad4 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 30 May 2025 14:34:21 +0530 Subject: [PATCH 15/15] reset: stuff on target branch [missed]. --- src/Appwrite/Platform/Tasks/ScheduleMessages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Tasks/ScheduleMessages.php b/src/Appwrite/Platform/Tasks/ScheduleMessages.php index 5e65f7a8a6..d23e3de575 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleMessages.php +++ b/src/Appwrite/Platform/Tasks/ScheduleMessages.php @@ -40,7 +40,7 @@ class ScheduleMessages extends ScheduleBase continue; } - \go(function () use ($schedule, $scheduledAt, $pools, $dbForPlatform) { + \go(function () use ($schedule, $scheduledAt, $dbForPlatform) { $queueForMessaging = new Messaging($this->publisher); $this->updateProjectAccess($schedule['project'], $dbForPlatform);