diff --git a/.env b/.env index d7aa8b8d9e..b7dd9e24f3 100644 --- a/.env +++ b/.env @@ -85,7 +85,7 @@ _APP_COMPUTE_RUNTIMES_NETWORK=runtimes _APP_EXECUTOR_SECRET=your-secret-key _APP_EXECUTOR_HOST=http://exc1/v1 _APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1 -_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.29 +_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.32 _APP_MAINTENANCE_INTERVAL=86400 _APP_MAINTENANCE_START_TIME=12:00 _APP_MAINTENANCE_RETENTION_CACHE=2592000 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/README-CN.md b/README-CN.md index 49a97aab53..a9a6b3c867 100644 --- a/README-CN.md +++ b/README-CN.md @@ -72,7 +72,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.7.3 + appwrite/appwrite:1.7.4 ``` ### Windows @@ -84,7 +84,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.7.3 + appwrite/appwrite:1.7.4 ``` #### PowerShell @@ -94,7 +94,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.7.3 + appwrite/appwrite:1.7.4 ``` 运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。 diff --git a/README.md b/README.md index d19d56fdd0..88d3fe89df 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.7.3 + appwrite/appwrite:1.7.4 ``` ### Windows @@ -90,7 +90,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.7.3 + appwrite/appwrite:1.7.4 ``` #### PowerShell @@ -100,7 +100,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.7.3 + appwrite/appwrite:1.7.4 ``` Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation. 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/collections/common.php b/app/config/collections/common.php index e77d5403d2..6de7eb224b 100644 --- a/app/config/collections/common.php +++ b/app/config/collections/common.php @@ -1439,13 +1439,6 @@ return [ 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], - [ - '$id' => ID::custom('_key_roles'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['roles'], - 'lengths' => [128], - 'orders' => [], - ], ], ], 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/config/platforms.php b/app/config/platforms.php index 4e705516fa..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, @@ -217,7 +217,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '6.2.3', + 'version' => '7.0.0', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, diff --git a/app/config/specs/open-api3-1.7.x-client.json b/app/config/specs/open-api3-1.7.x-client.json index abeab8a56f..bbb78c6c66 100644 --- a/app/config/specs/open-api3-1.7.x-client.json +++ b/app/config/specs/open-api3-1.7.x-client.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", diff --git a/app/config/specs/open-api3-1.7.x-console.json b/app/config/specs/open-api3-1.7.x-console.json index 19ce78915c..90ef137fc2 100644 --- a/app/config/specs/open-api3-1.7.x-console.json +++ b/app/config/specs/open-api3-1.7.x-console.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -8049,6 +8049,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -8204,7 +8227,10 @@ "type": "object" } } - } + }, + "required": [ + "documents" + ] } } } diff --git a/app/config/specs/open-api3-1.7.x-server.json b/app/config/specs/open-api3-1.7.x-server.json index db615d642c..1ae9328864 100644 --- a/app/config/specs/open-api3-1.7.x-server.json +++ b/app/config/specs/open-api3-1.7.x-server.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -7530,6 +7530,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -7688,7 +7711,10 @@ "type": "object" } } - } + }, + "required": [ + "documents" + ] } } } diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index abeab8a56f..bbb78c6c66 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 19ce78915c..90ef137fc2 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -8049,6 +8049,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -8204,7 +8227,10 @@ "type": "object" } } - } + }, + "required": [ + "documents" + ] } } } diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index db615d642c..1ae9328864 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -7530,6 +7530,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/components\/schemas\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -7688,7 +7711,10 @@ "type": "object" } } - } + }, + "required": [ + "documents" + ] } } } diff --git a/app/config/specs/swagger2-1.7.x-client.json b/app/config/specs/swagger2-1.7.x-client.json index 5c1b764e1e..92132151b4 100644 --- a/app/config/specs/swagger2-1.7.x-client.json +++ b/app/config/specs/swagger2-1.7.x-client.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", diff --git a/app/config/specs/swagger2-1.7.x-console.json b/app/config/specs/swagger2-1.7.x-console.json index 96705f3e61..e53a0dfb0b 100644 --- a/app/config/specs/swagger2-1.7.x-console.json +++ b/app/config/specs/swagger2-1.7.x-console.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -8178,6 +8178,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -8323,13 +8346,16 @@ "documents": { "type": "array", "description": "Array of document data as JSON objects. May contain partial documents.", - "default": [], + "default": null, "x-example": null, "items": { "type": "object" } } - } + }, + "required": [ + "documents" + ] } } ] diff --git a/app/config/specs/swagger2-1.7.x-server.json b/app/config/specs/swagger2-1.7.x-server.json index c033008417..083290bcc0 100644 --- a/app/config/specs/swagger2-1.7.x-server.json +++ b/app/config/specs/swagger2-1.7.x-server.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -7649,6 +7649,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -7797,13 +7820,16 @@ "documents": { "type": "array", "description": "Array of document data as JSON objects. May contain partial documents.", - "default": [], + "default": null, "x-example": null, "items": { "type": "object" } } - } + }, + "required": [ + "documents" + ] } } ] diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 5c1b764e1e..92132151b4 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 96705f3e61..e53a0dfb0b 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -8178,6 +8178,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -8323,13 +8346,16 @@ "documents": { "type": "array", "description": "Array of document data as JSON objects. May contain partial documents.", - "default": [], + "default": null, "x-example": null, "items": { "type": "object" } } - } + }, + "required": [ + "documents" + ] } } ] diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index c033008417..083290bcc0 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.7.0", + "version": "1.7.4", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -7649,6 +7649,29 @@ } ], "description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." + }, + { + "name": "createDocuments", + "auth": { + "Key": [] + }, + "parameters": [ + "databaseId", + "collectionId", + "documents" + ], + "required": [ + "databaseId", + "collectionId", + "documents" + ], + "responses": [ + { + "code": 201, + "model": "#\/definitions\/documentList" + } + ], + "description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console." } ], "auth": { @@ -7797,13 +7820,16 @@ "documents": { "type": "array", "description": "Array of document data as JSON objects. May contain partial documents.", - "default": [], + "default": null, "x-example": null, "items": { "type": "object" } } - } + }, + "required": [ + "documents" + ] } } ] diff --git a/app/config/template-runtimes.php b/app/config/template-runtimes.php index 8f1c0198c2..d1bb1a5b6a 100644 --- a/app/config/template-runtimes.php +++ b/app/config/template-runtimes.php @@ -14,7 +14,7 @@ return [ ], 'DART' => [ 'name' => 'dart', - 'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16'] + 'versions' => ['3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16'] ], 'GO' => [ 'name' => 'go', @@ -38,6 +38,6 @@ return [ ], 'FLUTTER' => [ 'name' => 'flutter', - 'versions' => ['3.24'] + 'versions' => ['3.32', '3.24'] ], ]; diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 4860f7a967..dcf547b688 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1347,17 +1347,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string ->inject('dbForProject') ->inject('queueForDatabase') ->inject('queueForEvents') - ->inject('plan') - ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, array $plan) { - if ($encrypt && !empty($plan) && !($plan['databasesAllowEncrypt'] ?? false)) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Encrypted string attributes are not available on your plan. Please upgrade to create encrypted string attributes.'); - } - if ($encrypt && $size < APP_DATABASE_ENCRYPT_SIZE_MIN) { - throw new Exception( - Exception::GENERAL_BAD_REQUEST, - "Size too small. Encrypted strings require a minimum size of " . APP_DATABASE_ENCRYPT_SIZE_MIN . " characters." - ); - } + ->action(function (string $databaseId, string $collectionId, string $key, ?int $size, ?bool $required, ?string $default, bool $array, bool $encrypt, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { // Ensure attribute default is within required size $validator = new Text($size, 0); if (!is_null($default) && !$validator->isValid($default)) { @@ -1378,7 +1368,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string 'array' => $array, 'filters' => $filters, ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); - $attribute->setAttribute('encrypt', $encrypt); + $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); @@ -2057,13 +2047,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') throw new Exception(Exception::GENERAL_QUERY_INVALID); } - foreach ($attributes as $attribute) { - if ($attribute->getAttribute('type') === Database::VAR_STRING) { - $filters = $attribute->getAttribute('filters', []); - $attribute->setAttribute('encrypt', in_array('encrypt', $filters)); - } - } - $response->dynamic(new Document([ 'attributes' => $attributes, 'total' => $total, @@ -2128,7 +2111,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') $type = $attribute->getAttribute('type'); $format = $attribute->getAttribute('format'); $options = $attribute->getAttribute('options', []); - $filters = $attribute->getAttribute('filters', []); + foreach ($options as $key => $option) { $attribute->setAttribute($key, $option); } @@ -2148,7 +2131,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') }, default => Response::MODEL_ATTRIBUTE, }; - $attribute->setAttribute('encrypt', in_array('encrypt', $filters)); + $response->dynamic($attribute, $model); }); @@ -4598,7 +4581,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId/documents') )) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', true, ['plan']) + ->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', false, ['plan']) ->inject('response') ->inject('dbForProject') ->inject('queueForStatsUsage') 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/app/controllers/api/teams.php b/app/controllers/api/teams.php index 49d9005c54..7e52b16911 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1097,10 +1097,13 @@ 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)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'There must be at least one owner in the organization.'); + // 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 in the organization.'); } } @@ -1315,10 +1318,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, 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 +1331,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); } @@ -1342,6 +1347,29 @@ 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 + ); + + // Is the deletion being requested by the user on their own membership and they are also the owner? + $isSelfOwner = + in_array('owner', $membership->getAttribute('roles')) && + $membership->getAttribute('userInternalId') === $user->getInternalId(); + + if ($ownersCount === 1 && $isSelfOwner) { + /* Prevent removal if the user is the only owner. */ + throw new Exception(Exception::MEMBERSHIP_DELETION_PROHIBITED, 'There must be at least one owner in the organization.'); + } + } + try { $dbForProject->deleteDocument('memberships', $membership->getId()); } catch (AuthorizationException $exception) { @@ -1350,15 +1378,15 @@ 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)); } $queueForEvents - ->setParam('userId', $user->getId()) ->setParam('teamId', $team->getId()) + ->setParam('userId', $profile->getId()) ->setParam('membershipId', $membership->getId()) ->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP)) ; diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 571c7ddca7..c8a0a36a85 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -293,7 +293,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId // VCS branch preview if (!empty($providerBranch)) { - $domain = "branch-{$providerBranch}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}"; + $branchPrefix = substr($providerBranch, 0, 16); + if (strlen($providerBranch) > 16) { + $remainingChars = substr($providerBranch, 16); + $branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7); + } + $resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7); + $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; $ruleId = md5($domain); try { Authorization::skip( @@ -324,7 +330,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId // VCS commit preview if (!empty($providerCommitHash)) { - $domain = "commit-{$providerCommitHash}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}"; + $domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}"; $ruleId = md5($domain); try { Authorization::skip( @@ -1209,6 +1215,7 @@ App::post('/v1/vcs/github/events') if ($event == $github::EVENT_PUSH) { $providerBranchCreated = $parsedPayload["branchCreated"] ?? false; + $providerBranchDeleted = $parsedPayload["branchDeleted"] ?? false; $providerBranch = $parsedPayload["branch"] ?? ''; $providerBranchUrl = $parsedPayload["branchUrl"] ?? ''; $providerRepositoryId = $parsedPayload["repositoryId"] ?? ''; @@ -1230,8 +1237,8 @@ App::post('/v1/vcs/github/events') Query::limit(100), ])); - // create new deployment only on push and not when branch is created - if (!$providerBranchCreated) { + // create new deployment only on push and not when branch is created or deleted + if (!$providerBranchCreated && !$providerBranchDeleted) { $createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request); } } elseif ($event == $github::EVENT_INSTALLATION) { diff --git a/app/controllers/general.php b/app/controllers/general.php index bff701792e..5beac9e1e8 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -749,12 +749,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw return false; } elseif ($type === 'redirect') { $url = $rule->getAttribute('redirectUrl', ''); - - $query = ($swooleRequest->server['query_string'] ?? ''); - if (!empty($query)) { - $url .= '?' . $query; - } - $response->redirect($url, \intval($rule->getAttribute('redirectStatusCode', 301))); return true; } else { diff --git a/app/init/constants.php b/app/init/constants.php index e1b595a10f..99881a4381 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -37,8 +37,8 @@ const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours -const APP_CACHE_BUSTER = 4319; -const APP_VERSION_STABLE = '1.7.3'; +const APP_CACHE_BUSTER = 4320; +const APP_VERSION_STABLE = '1.7.4'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; @@ -51,7 +51,6 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes const APP_DATABASE_QUERY_MAX_VALUES = 500; -const APP_DATABASE_ENCRYPT_SIZE_MIN = 150; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_SITES = '/storage/sites'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; diff --git a/app/init/database/filters.php b/app/init/database/filters.php index c470329706..f110fe1554 100644 --- a/app/init/database/filters.php +++ b/app/init/database/filters.php @@ -77,21 +77,12 @@ Database::addFilter( ]); foreach ($attributes as $attribute) { - $attributeType = $attribute->getAttribute('type'); - - switch ($attributeType) { - case Database::VAR_RELATIONSHIP: - $options = $attribute->getAttribute('options'); - foreach ($options as $key => $value) { - $attribute->setAttribute($key, $value); - } - $attribute->removeAttribute('options'); - break; - - case Database::VAR_STRING: - $filters = $attribute->getAttribute('filters', []); - $attribute->setAttribute('encrypt', in_array('encrypt', $filters)); - break; + if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { + $options = $attribute->getAttribute('options'); + foreach ($options as $key => $value) { + $attribute->setAttribute($key, $value); + } + $attribute->removeAttribute('options'); } } diff --git a/app/views/general/404.phtml b/app/views/general/404.phtml index 5e63344c8a..2a9ff7f312 100644 --- a/app/views/general/404.phtml +++ b/app/views/general/404.phtml @@ -151,37 +151,6 @@
- -
-

Powered by

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
\ No newline at end of file diff --git a/app/views/general/error.phtml b/app/views/general/error.phtml index de1c2b6541..a427f1d5e8 100644 --- a/app/views/general/error.phtml +++ b/app/views/general/error.phtml @@ -512,36 +512,6 @@ switch ($type) { -
-

Powered by

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
\ No newline at end of file diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index d78bca7a38..2d8f2b35ab 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -177,7 +177,7 @@ $image = $this->getParam('image', ''); appwrite-console: <<: *x-logging container_name: appwrite-console - image: /console:6.0.11 + image: /console:6.0.13 restart: unless-stopped networks: - appwrite @@ -864,7 +864,7 @@ $image = $this->getParam('image', ''); <<: *x-logging restart: unless-stopped stop_signal: SIGINT - image: openruntimes/executor:0.7.14 + image: openruntimes/executor:0.7.20 networks: - appwrite - runtimes diff --git a/app/worker.php b/app/worker.php index c92c48936f..c25608cd5e 100644 --- a/app/worker.php +++ b/app/worker.php @@ -18,7 +18,9 @@ use Appwrite\Event\StatsUsage; use Appwrite\Event\Webhook; use Appwrite\Platform\Appwrite; use Executor\Executor; +use Swoole\Process; use Swoole\Runtime; +use Swoole\Timer; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\Cache\Adapter\Pool as CachePool; use Utopia\Cache\Adapter\Sharding; @@ -482,8 +484,15 @@ $worker }); $worker->workerStart() - ->action(function () use ($workerName) { - Console::info("Worker $workerName started"); + ->action(function () use ($worker, $workerName) { + Console::info("Worker $workerName started"); + + Process::signal(SIGTERM, function () use ($worker, $workerName) { + Console::info("Stopping worker $workerName."); + + $worker->stop(); + Timer::clearAll(); + }); }); $worker->start(); diff --git a/bin/doctor b/bin/doctor index b2a4547156..92b3cf6bf8 100755 --- a/bin/doctor +++ b/bin/doctor @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php doctor $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php doctor $@ diff --git a/bin/install b/bin/install index e669e91e6b..126d5373bf 100755 --- a/bin/install +++ b/bin/install @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php install $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php install $@ diff --git a/bin/maintenance b/bin/maintenance index 099551cb32..70273feb61 100644 --- a/bin/maintenance +++ b/bin/maintenance @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php maintenance $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php maintenance $@ diff --git a/bin/migrate b/bin/migrate index 28ebbd19e7..32bf7ee2f4 100755 --- a/bin/migrate +++ b/bin/migrate @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php migrate $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php migrate $@ diff --git a/bin/queue-count-failed b/bin/queue-count-failed index ca8f2b4291..93a6b3136c 100644 --- a/bin/queue-count-failed +++ b/bin/queue-count-failed @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php queue-count --type=failed $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php queue-count --type=failed $@ diff --git a/bin/queue-count-processing b/bin/queue-count-processing index 325d86111d..18e89664bd 100644 --- a/bin/queue-count-processing +++ b/bin/queue-count-processing @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php queue-count --type=processing $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php queue-count --type=processing $@ diff --git a/bin/queue-count-success b/bin/queue-count-success index 34fc54b4c1..b38bfa2159 100644 --- a/bin/queue-count-success +++ b/bin/queue-count-success @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php queue-count --type=success $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php queue-count --type=success $@ diff --git a/bin/queue-retry b/bin/queue-retry index f9473e6b07..2224a66b3b 100644 --- a/bin/queue-retry +++ b/bin/queue-retry @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php queue-retry $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php queue-retry $@ diff --git a/bin/realtime b/bin/realtime index e43dc269e0..debd4baf2a 100644 --- a/bin/realtime +++ b/bin/realtime @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/realtime.php $@ \ No newline at end of file +exec php /usr/src/code/app/realtime.php $@ diff --git a/bin/schedule-executions b/bin/schedule-executions index f239cad206..5d503e374c 100644 --- a/bin/schedule-executions +++ b/bin/schedule-executions @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php schedule-executions $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php schedule-executions $@ diff --git a/bin/schedule-functions b/bin/schedule-functions index 10edbe8226..beca1a0420 100644 --- a/bin/schedule-functions +++ b/bin/schedule-functions @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php schedule-functions $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php schedule-functions $@ diff --git a/bin/schedule-messages b/bin/schedule-messages index fa7219f6ea..3f17a279b5 100644 --- a/bin/schedule-messages +++ b/bin/schedule-messages @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php schedule-messages $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php schedule-messages $@ diff --git a/bin/screenshot b/bin/screenshot index 4d8ceb998f..aef15eb96f 100755 --- a/bin/screenshot +++ b/bin/screenshot @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php screenshot $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php screenshot $@ \ No newline at end of file diff --git a/bin/sdks b/bin/sdks index ab73414829..3180813ea1 100644 --- a/bin/sdks +++ b/bin/sdks @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php sdks $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php sdks $@ diff --git a/bin/specs b/bin/specs index e77d1487d4..52875a1675 100644 --- a/bin/specs +++ b/bin/specs @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php specs $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php specs $@ diff --git a/bin/ssl b/bin/ssl index 83dcf6a026..0cc12375d0 100755 --- a/bin/ssl +++ b/bin/ssl @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php ssl $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php ssl $@ diff --git a/bin/stats-resources b/bin/stats-resources index 3104bab896..9cc67fb4a6 100644 --- a/bin/stats-resources +++ b/bin/stats-resources @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php stats-resources $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php stats-resources $@ diff --git a/bin/test b/bin/test index a2153fc536..c3b0d9b74c 100755 --- a/bin/test +++ b/bin/test @@ -1,3 +1,3 @@ #!/bin/sh -/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@ \ No newline at end of file +exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@ diff --git a/bin/upgrade b/bin/upgrade index ce32b9ca30..fd1f35b65a 100755 --- a/bin/upgrade +++ b/bin/upgrade @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php upgrade $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php upgrade $@ diff --git a/bin/vars b/bin/vars index 19e3f1ebf2..238d004675 100644 --- a/bin/vars +++ b/bin/vars @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/cli.php vars $@ \ No newline at end of file +exec php /usr/src/code/app/cli.php vars $@ diff --git a/bin/worker-audits b/bin/worker-audits index 3df65d65e8..9bf81b93e2 100644 --- a/bin/worker-audits +++ b/bin/worker-audits @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php audits $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php audits $@ diff --git a/bin/worker-builds b/bin/worker-builds index 3400111cb5..7ddf5792de 100644 --- a/bin/worker-builds +++ b/bin/worker-builds @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php builds $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php builds $@ diff --git a/bin/worker-certificates b/bin/worker-certificates index 901688c4c8..958627bc33 100755 --- a/bin/worker-certificates +++ b/bin/worker-certificates @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php certificates $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php certificates $@ diff --git a/bin/worker-databases b/bin/worker-databases index 61e09aa9f1..235c16927c 100644 --- a/bin/worker-databases +++ b/bin/worker-databases @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php databases $@ +exec php /usr/src/code/app/worker.php databases $@ diff --git a/bin/worker-deletes b/bin/worker-deletes index 7c9793e6cb..c9711a4e66 100644 --- a/bin/worker-deletes +++ b/bin/worker-deletes @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php deletes $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php deletes $@ diff --git a/bin/worker-functions b/bin/worker-functions index 4757b1b72a..e82506d0e2 100644 --- a/bin/worker-functions +++ b/bin/worker-functions @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php functions $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php functions $@ diff --git a/bin/worker-mails b/bin/worker-mails index fee8a96da7..ffb4f7ea77 100644 --- a/bin/worker-mails +++ b/bin/worker-mails @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php mails $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php mails $@ diff --git a/bin/worker-messaging b/bin/worker-messaging index e6edf80f06..6f39348419 100644 --- a/bin/worker-messaging +++ b/bin/worker-messaging @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php messaging $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php messaging $@ diff --git a/bin/worker-migrations b/bin/worker-migrations index 32d4aef468..2728d9d22c 100644 --- a/bin/worker-migrations +++ b/bin/worker-migrations @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php migrations $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php migrations $@ diff --git a/bin/worker-stats-resources b/bin/worker-stats-resources index 9c5d2bebff..70bfacbe52 100644 --- a/bin/worker-stats-resources +++ b/bin/worker-stats-resources @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php stats-resources $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php stats-resources $@ diff --git a/bin/worker-stats-usage b/bin/worker-stats-usage index 2c267d805e..e6dd849a48 100644 --- a/bin/worker-stats-usage +++ b/bin/worker-stats-usage @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php stats-usage $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php stats-usage $@ diff --git a/bin/worker-webhooks b/bin/worker-webhooks index 93f8027a81..63fc13e3c5 100644 --- a/bin/worker-webhooks +++ b/bin/worker-webhooks @@ -1,3 +1,3 @@ #!/bin/sh -php /usr/src/code/app/worker.php webhooks $@ \ No newline at end of file +exec php /usr/src/code/app/worker.php webhooks $@ diff --git a/composer.json b/composer.json index 7e445cd36b..b7f7809219 100644 --- a/composer.json +++ b/composer.json @@ -61,13 +61,13 @@ "utopia-php/image": "0.8.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.6.*", - "utopia-php/messaging": "0.17.*", + "utopia-php/messaging": "0.18.*", "utopia-php/migration": "0.9.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.7.*", "utopia-php/pools": "0.8.*", "utopia-php/preloader": "0.2.*", - "utopia-php/queue": "0.10.*", + "utopia-php/queue": "0.11.*", "utopia-php/registry": "0.5.*", "utopia-php/storage": "0.18.*", "utopia-php/swoole": "0.8.*", @@ -76,17 +76,17 @@ "utopia-php/vcs": "0.10.*", "utopia-php/websocket": "0.3.*", "matomo/device-detector": "6.1.*", - "dragonmantank/cron-expression": "3.3.2", - "phpmailer/phpmailer": "6.9.1", - "chillerlan/php-qrcode": "4.3.4", - "adhocore/jwt": "1.1.2", + "dragonmantank/cron-expression": "3.3.*", + "phpmailer/phpmailer": "6.9.*", + "chillerlan/php-qrcode": "4.3.*", + "adhocore/jwt": "1.1.*", "spomky-labs/otphp": "^10.0", "webonyx/graphql-php": "14.11.*", "league/csv": "9.14.*" }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "0.40.*", + "appwrite/sdk-generator": "0.41.*", "phpunit/phpunit": "9.*", "swoole/ide-helper": "5.1.2", "phpstan/phpstan": "1.8.*", diff --git a/composer.lock b/composer.lock index 0ffe052061..9e757f9db8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,27 @@ "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": "3230eea9e592f0b712e78c1ef5f5e244", "packages": [ { "name": "adhocore/jwt", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/adhocore/php-jwt.git", - "reference": "6c434af7170090bb7a8880d2bc220a2254ba7899" + "reference": "ad417603d9d45578b6af2089ad5b78f101c82367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/adhocore/php-jwt/zipball/6c434af7170090bb7a8880d2bc220a2254ba7899", - "reference": "6c434af7170090bb7a8880d2bc220a2254ba7899", + "url": "https://api.github.com/repos/adhocore/php-jwt/zipball/ad417603d9d45578b6af2089ad5b78f101c82367", + "reference": "ad417603d9d45578b6af2089ad5b78f101c82367", "shasum": "" }, "require": { "php": "^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.5 || ^7.5" + "phpunit/phpunit": "^7.5 || ^8.5" }, "type": "library", "autoload": { @@ -53,15 +53,19 @@ ], "support": { "issues": "https://github.com/adhocore/php-jwt/issues", - "source": "https://github.com/adhocore/php-jwt/tree/1.1.2" + "source": "https://github.com/adhocore/php-jwt/tree/1.1.3" }, "funding": [ { "url": "https://paypal.me/ji10", "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" } ], - "time": "2021-02-20T09:56:44+00:00" + "time": "2025-02-18T01:00:50+00:00" }, { "name": "appwrite/appwrite", @@ -157,16 +161,16 @@ }, { "name": "appwrite/php-runtimes", - "version": "0.19.0", + "version": "0.19.1", "source": { "type": "git", "url": "https://github.com/appwrite/runtimes.git", - "reference": "8d21483efc19b9d977e323188989ee67a188464b" + "reference": "7bd0cc3cb97de625d7b07230bd91b121f88e72ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/runtimes/zipball/8d21483efc19b9d977e323188989ee67a188464b", - "reference": "8d21483efc19b9d977e323188989ee67a188464b", + "url": "https://api.github.com/repos/appwrite/runtimes/zipball/7bd0cc3cb97de625d7b07230bd91b121f88e72ae", + "reference": "7bd0cc3cb97de625d7b07230bd91b121f88e72ae", "shasum": "" }, "require": { @@ -206,9 +210,9 @@ ], "support": { "issues": "https://github.com/appwrite/runtimes/issues", - "source": "https://github.com/appwrite/runtimes/tree/0.19.0" + "source": "https://github.com/appwrite/runtimes/tree/0.19.1" }, - "time": "2025-03-25T22:37:51+00:00" + "time": "2025-05-27T07:12:56+00:00" }, { "name": "beberlei/assert", @@ -279,16 +283,16 @@ }, { "name": "brick/math", - "version": "0.12.3", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "shasum": "" }, "require": { @@ -327,7 +331,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.13.1" }, "funding": [ { @@ -335,7 +339,7 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2025-03-29T13:50:30+00:00" }, { "name": "chillerlan/php-qrcode", @@ -565,16 +569,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", "shasum": "" }, "require": { @@ -614,7 +618,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" }, "funding": [ { @@ -622,7 +626,7 @@ "type": "github" } ], - "time": "2022-09-10T18:51:20+00:00" + "time": "2023-08-10T19:36:49+00:00" }, { "name": "giggsey/libphonenumber-for-php-lite", @@ -709,16 +713,16 @@ }, { "name": "google/protobuf", - "version": "v4.31.0", + "version": "v4.31.1", "source": { "type": "git", "url": "https://github.com/protocolbuffers/protobuf-php.git", - "reference": "d59e31ce4bf0e4b48728e90c4d880839edb5be07" + "reference": "2b028ce8876254e2acbeceea7d9b573faad41864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/d59e31ce4bf0e4b48728e90c4d880839edb5be07", - "reference": "d59e31ce4bf0e4b48728e90c4d880839edb5be07", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/2b028ce8876254e2acbeceea7d9b573faad41864", + "reference": "2b028ce8876254e2acbeceea7d9b573faad41864", "shasum": "" }, "require": { @@ -747,9 +751,9 @@ "proto" ], "support": { - "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.0" + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.31.1" }, - "time": "2025-05-14T16:17:23+00:00" + "time": "2025-05-28T18:52:35+00:00" }, { "name": "league/csv", @@ -2319,20 +2323,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.8.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -2341,26 +2345,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -2395,19 +2396,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.8.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-06-01T06:28:46+00:00" }, { "name": "spomky-labs/otphp", @@ -2553,16 +2544,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.4", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" + "reference": "57e4fb86314015a695a750ace358d07a7e37b8a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", - "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "url": "https://api.github.com/repos/symfony/http-client/zipball/57e4fb86314015a695a750ace358d07a7e37b8a9", + "reference": "57e4fb86314015a695a750ace358d07a7e37b8a9", "shasum": "" }, "require": { @@ -2628,7 +2619,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.4" + "source": "https://github.com/symfony/http-client/tree/v7.3.0" }, "funding": [ { @@ -2644,7 +2635,7 @@ "type": "tidelift" } ], - "time": "2025-02-13T10:27:23+00:00" + "time": "2025-05-02T08:23:16+00:00" }, { "name": "symfony/http-client-contracts", @@ -3499,16 +3490,16 @@ }, { "name": "utopia-php/database", - "version": "0.69.5", + "version": "0.69.7", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "4abe53609dfc23b2ea82884d12b149df6a8af2f5" + "reference": "3db51ec544b3f287511209e34bab3f5ec85fa56e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/4abe53609dfc23b2ea82884d12b149df6a8af2f5", - "reference": "4abe53609dfc23b2ea82884d12b149df6a8af2f5", + "url": "https://api.github.com/repos/utopia-php/database/zipball/3db51ec544b3f287511209e34bab3f5ec85fa56e", + "reference": "3db51ec544b3f287511209e34bab3f5ec85fa56e", "shasum": "" }, "require": { @@ -3549,9 +3540,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.69.5" + "source": "https://github.com/utopia-php/database/tree/0.69.7" }, - "time": "2025-05-17T08:01:51+00:00" + "time": "2025-05-30T14:41:25+00:00" }, { "name": "utopia-php/detector", @@ -3948,16 +3939,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.17.0", + "version": "0.18.0", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "c51915d0e030db3a3add37f1561751d18b2d9a85" + "reference": "c151aa5d4d475c788ca15c210b5b2017e21c41d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/c51915d0e030db3a3add37f1561751d18b2d9a85", - "reference": "c51915d0e030db3a3add37f1561751d18b2d9a85", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/c151aa5d4d475c788ca15c210b5b2017e21c41d6", + "reference": "c151aa5d4d475c788ca15c210b5b2017e21c41d6", "shasum": "" }, "require": { @@ -3993,9 +3984,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.17.0" + "source": "https://github.com/utopia-php/messaging/tree/0.18.0" }, - "time": "2025-05-12T16:14:08+00:00" + "time": "2025-05-15T05:00:03+00:00" }, { "name": "utopia-php/migration", @@ -4105,16 +4096,16 @@ }, { "name": "utopia-php/platform", - "version": "0.7.7", + "version": "0.7.8", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "8c43cd866148a7c4c495e3401268429e338004b3" + "reference": "e3a4536c46f10988b1a446ec6b8dd8a9914be854" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/8c43cd866148a7c4c495e3401268429e338004b3", - "reference": "8c43cd866148a7c4c495e3401268429e338004b3", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/e3a4536c46f10988b1a446ec6b8dd8a9914be854", + "reference": "e3a4536c46f10988b1a446ec6b8dd8a9914be854", "shasum": "" }, "require": { @@ -4123,7 +4114,7 @@ "php": ">=8.0", "utopia-php/cli": "0.15.*", "utopia-php/framework": "0.33.*", - "utopia-php/queue": "0.10.*" + "utopia-php/queue": "0.11.*" }, "require-dev": { "laravel/pint": "1.*", @@ -4149,9 +4140,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.7.7" + "source": "https://github.com/utopia-php/platform/tree/0.7.8" }, - "time": "2025-05-20T09:23:44+00:00" + "time": "2025-05-30T10:05:43+00:00" }, { "name": "utopia-php/pools", @@ -4260,16 +4251,16 @@ }, { "name": "utopia-php/queue", - "version": "0.10.0", + "version": "0.11.1", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "0eccc559168ea72241c39a4c482d868314666be1" + "reference": "498bbbef418b1db71b51e1bb62f5d1d752ddd8d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/0eccc559168ea72241c39a4c482d868314666be1", - "reference": "0eccc559168ea72241c39a4c482d868314666be1", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/498bbbef418b1db71b51e1bb62f5d1d752ddd8d6", + "reference": "498bbbef418b1db71b51e1bb62f5d1d752ddd8d6", "shasum": "" }, "require": { @@ -4320,9 +4311,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.10.0" + "source": "https://github.com/utopia-php/queue/tree/0.11.1" }, - "time": "2025-04-17T12:15:52+00:00" + "time": "2025-05-30T11:50:34+00:00" }, { "name": "utopia-php/registry", @@ -4593,16 +4584,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.10.2", + "version": "0.10.4", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "1f9823ebcb8fd098607de0074f18f48e28985012" + "reference": "f635b368909eb3c3fe57344fe43525e74e8fdc03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/1f9823ebcb8fd098607de0074f18f48e28985012", - "reference": "1f9823ebcb8fd098607de0074f18f48e28985012", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/f635b368909eb3c3fe57344fe43525e74e8fdc03", + "reference": "f635b368909eb3c3fe57344fe43525e74e8fdc03", "shasum": "" }, "require": { @@ -4636,9 +4627,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.10.2" + "source": "https://github.com/utopia-php/vcs/tree/0.10.4" }, - "time": "2025-04-17T04:35:25+00:00" + "time": "2025-06-02T09:18:36+00:00" }, { "name": "utopia-php/websocket", @@ -4816,16 +4807,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.40.19", + "version": "0.41.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "05b53cf30c59fe5934d57207fefbc8ae8feb5dbf" + "reference": "96316272a3cee1a3abf5b9f05ae49ebbff03725e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/05b53cf30c59fe5934d57207fefbc8ae8feb5dbf", - "reference": "05b53cf30c59fe5934d57207fefbc8ae8feb5dbf", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/96316272a3cee1a3abf5b9f05ae49ebbff03725e", + "reference": "96316272a3cee1a3abf5b9f05ae49ebbff03725e", "shasum": "" }, "require": { @@ -4861,9 +4852,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.19" + "source": "https://github.com/appwrite/sdk-generator/tree/0.41.0" }, - "time": "2025-05-24T22:49:50+00:00" + "time": "2025-05-26T09:47:45+00:00" }, { "name": "doctrine/annotations", @@ -5340,16 +5331,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -5392,9 +5383,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "phar-io/manifest", @@ -7262,23 +7253,24 @@ }, { "name": "symfony/console", - "version": "v7.2.6", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" + "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", - "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", + "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^7.2" }, "conflict": { "symfony/dependency-injection": "<6.4", @@ -7335,7 +7327,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.6" + "source": "https://github.com/symfony/console/tree/v7.3.0" }, "funding": [ { @@ -7351,11 +7343,11 @@ "type": "tidelift" } ], - "time": "2025-04-07T19:09:28+00:00" + "time": "2025-05-24T10:34:04+00:00" }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -7401,7 +7393,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" }, "funding": [ { @@ -7421,16 +7413,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", "shasum": "" }, "require": { @@ -7465,7 +7457,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v7.3.0" }, "funding": [ { @@ -7481,20 +7473,20 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2024-12-30T19:00:26+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", - "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", + "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", "shasum": "" }, "require": { @@ -7532,7 +7524,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" }, "funding": [ { @@ -7548,7 +7540,7 @@ "type": "tidelift" } ], - "time": "2024-11-20T11:17:29+00:00" + "time": "2025-04-04T13:12:05+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7866,16 +7858,16 @@ }, { "name": "symfony/process", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", "shasum": "" }, "require": { @@ -7907,7 +7899,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.5" + "source": "https://github.com/symfony/process/tree/v7.3.0" }, "funding": [ { @@ -7923,20 +7915,20 @@ "type": "tidelift" } ], - "time": "2025-03-13T12:21:46+00:00" + "time": "2025-04-17T09:11:12+00:00" }, { "name": "symfony/string", - "version": "v7.2.6", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", - "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", "shasum": "" }, "require": { @@ -7994,7 +7986,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.6" + "source": "https://github.com/symfony/string/tree/v7.3.0" }, "funding": [ { @@ -8010,7 +8002,7 @@ "type": "tidelift" } ], - "time": "2025-04-20T20:18:16+00:00" + "time": "2025-04-20T20:19:01+00:00" }, { "name": "textalk/websocket", diff --git a/docker-compose.yml b/docker-compose.yml index 847e2fa72c..29a43aca91 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -213,7 +213,7 @@ services: appwrite-console: <<: *x-logging container_name: appwrite-console - image: appwrite/console:6.0.11 + image: appwrite/console:6.0.32 restart: unless-stopped networks: - appwrite @@ -951,7 +951,7 @@ services: hostname: exc1 <<: *x-logging stop_signal: SIGINT - image: openruntimes/executor:0.7.16 + image: openruntimes/executor:0.7.20 restart: unless-stopped networks: - appwrite diff --git a/docs/examples/1.7.x/console-cli/examples/databases/upsert-document.md b/docs/examples/1.7.x/console-cli/examples/databases/upsert-document.md new file mode 100644 index 0000000000..40932014ba --- /dev/null +++ b/docs/examples/1.7.x/console-cli/examples/databases/upsert-document.md @@ -0,0 +1,6 @@ +appwrite databases upsertDocument \ + --databaseId \ + --collectionId \ + --documentId \ + --data '{ "key": "value" }' \ + diff --git a/docs/examples/1.7.x/console-cli/examples/databases/upsert-documents.md b/docs/examples/1.7.x/console-cli/examples/databases/upsert-documents.md index 3d0bd165d5..cb1677b14c 100644 --- a/docs/examples/1.7.x/console-cli/examples/databases/upsert-documents.md +++ b/docs/examples/1.7.x/console-cli/examples/databases/upsert-documents.md @@ -1,4 +1,4 @@ appwrite databases upsertDocuments \ --databaseId \ --collectionId \ - + --documents one two three 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/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'; diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index a2b1109eb3..26b0a0301f 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -88,6 +88,7 @@ abstract class Migration '1.7.1' => 'V22', '1.7.2' => 'V22', '1.7.3' => 'V22', + '1.7.4' => 'V22', ]; /** diff --git a/src/Appwrite/Migration/Version/V22.php b/src/Appwrite/Migration/Version/V22.php index 2a6a64ed48..cfe4f1fe57 100644 --- a/src/Appwrite/Migration/Version/V22.php +++ b/src/Appwrite/Migration/Version/V22.php @@ -150,9 +150,9 @@ class V22 extends Migration ]; foreach ($indexes as $index) { try { - $this->createIndexFromCollection($this->dbForProject, $id, $index); + $this->dbForProject->deleteIndex($id, $index); } catch (Throwable $th) { - Console::warning("Failed to create index \"$index\" from {$id}: {$th->getMessage()}"); + Console::warning("Failed to delete index \"$index\" from {$id}: {$th->getMessage()}"); } } $this->dbForProject->purgeCachedCollection($id); diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 47529a142b..0541df551a 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Compute; use Appwrite\Event\Build; use Appwrite\Extend\Exception; -use Appwrite\Query; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -235,38 +234,4 @@ class Base extends Action return $deployment; } - - protected function listRules(Document $project, array $queries, Database $database, callable $callback): void - { - $limit = 100; - $cursor = null; - - do { - $queries = \array_merge([ - Query::limit($limit), - Query::equal("projectInternalId", [$project->getInternalId()]) - ], $queries); - - if ($cursor !== null) { - $queries[] = Query::cursorAfter($cursor); - } - - $results = $database->find('rules', $queries); - - $total = \count($results); - if ($total > 0) { - $cursor = $results[$total - 1]; - } - - if ($total < $limit) { - $cursor = null; - } - - foreach ($results as $document) { - if (is_callable($callback)) { - $callback($document); - } - } - } while (!\is_null($cursor)); - } } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php index 6de71cfae6..e840441774 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployment/Update.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Functions\Http\Functions\Deployment; use Appwrite\Event\Event; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Compute\Base; -use Appwrite\Query; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; @@ -13,6 +12,7 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Platform\Action; @@ -89,8 +89,6 @@ class Update extends Base throw new Exception(Exception::BUILD_NOT_READY); } - $oldDeploymentInternalId = $function->getAttribute('deploymentInternalId', ''); - $function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ 'deploymentInternalId' => $deployment->getInternalId(), 'deploymentId' => $deployment->getId(), @@ -106,24 +104,20 @@ class Update extends Base Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); $queries = [ - Query::equal('trigger', 'manual'), + Query::equal('trigger', ['manual']), Query::equal("type", ["deployment"]), Query::equal("deploymentResourceType", ["function"]), Query::equal("deploymentResourceInternalId", [$function->getInternalId()]), + Query::equal("deploymentVcsProviderBranch", [""]), + Query::equal("projectInternalId", [$project->getInternalId()]) ]; - if (empty($oldDeploymentInternalId)) { - $queries[] = Query::equal("deploymentInternalId", [""]); - } else { - $queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]); - } - - $this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) { + Authorization::skip(fn () => $dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) { $rule = $rule ->setAttribute('deploymentId', $deployment->getId()) ->setAttribute('deploymentInternalId', $deployment->getInternalId()); - $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); - }); + Authorization::skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), $rule)); + }, $queries)); $queueForEvents ->setParam('functionId', $function->getId()) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 0ecf1d3f73..de5543c9f3 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -1075,8 +1075,6 @@ class Builds extends Action $resource->setAttribute('live', true); switch ($resource->getCollection()) { case 'functions': - $oldDeploymentInternalId = $resource->getAttribute('deploymentInternalId', ''); - $resource->setAttribute('deploymentId', $deployment->getId()); $resource->setAttribute('deploymentInternalId', $deployment->getInternalId()); $resource->setAttribute('deploymentCreatedAt', $deployment->getCreatedAt()); @@ -1088,26 +1086,20 @@ class Builds extends Action Query::equal("deploymentResourceInternalId", [$resource->getInternalId()]), Query::equal('deploymentResourceType', ['function']), Query::equal('trigger', ['manual']), + Query::equal('deploymentVcsProviderBranch', ['']), + Query::equal("projectInternalId", [$project->getInternalId()]) ]; - if (empty($oldDeploymentInternalId)) { - $queries[] = Query::equal("deploymentInternalId", [""]); - } else { - $queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]); - } - $rulesUpdated = false; - $this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment, &$rulesUpdated) { + $dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment, &$rulesUpdated) { $rulesUpdated = true; $rule = $rule ->setAttribute('deploymentId', $deployment->getId()) ->setAttribute('deploymentInternalId', $deployment->getInternalId()); $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); - }); + }, $queries); break; case 'sites': - $oldDeploymentInternalId = $resource->getAttribute('deploymentInternalId', ''); - $resource->setAttribute('deploymentId', $deployment->getId()); $resource->setAttribute('deploymentInternalId', $deployment->getInternalId()); $resource->setAttribute('deploymentScreenshotDark', $deployment->getAttribute('screenshotDark', '')); @@ -1120,20 +1112,16 @@ class Builds extends Action Query::equal("deploymentResourceInternalId", [$resource->getInternalId()]), Query::equal('deploymentResourceType', ['site']), Query::equal('trigger', ['manual']), + Query::equal('deploymentVcsProviderBranch', ['']), + Query::equal("projectInternalId", [$project->getInternalId()]) ]; - if (empty($oldDeploymentInternalId)) { - $queries[] = Query::equal("deploymentInternalId", [""]); - } else { - $queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]); - } - - $this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) { + $dbForPlatform->forEach('rules', function (Document $rule) use ($dbForPlatform, $deployment) { $rule = $rule ->setAttribute('deploymentId', $deployment->getId()) ->setAttribute('deploymentInternalId', $deployment->getInternalId()); $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); - }); + }, $queries); break; } @@ -1144,7 +1132,13 @@ class Builds extends Action $branchName = $deployment->getAttribute('providerBranch'); if (!empty($branchName)) { $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); - $domain = "branch-{$branchName}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}"; + $branchPrefix = substr($branchName, 0, 16); + if (strlen($branchName) > 16) { + $remainingChars = substr($branchName, 16); + $branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7); + } + $resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7); + $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; $ruleId = md5($domain); try { @@ -1175,19 +1169,22 @@ class Builds extends Action $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); } - $this->listRules($project, [ + $queries = [ Query::equal("projectInternalId", [$project->getInternalId()]), Query::equal("type", ["deployment"]), Query::equal("deploymentResourceInternalId", [$resource->getInternalId()]), Query::equal('deploymentResourceType', ['site']), Query::equal("deploymentVcsProviderBranch", [$branchName]), Query::equal("trigger", ['manual']), - ], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) { + Query::equal("projectInternalId", [$project->getInternalId()]) + ]; + + $dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) { $rule = $rule ->setAttribute('deploymentId', $deployment->getId()) ->setAttribute('deploymentInternalId', $deployment->getInternalId()); $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); - }); + }, $queries); } } @@ -1490,38 +1487,4 @@ class Builds extends Action } } } - - protected function listRules(Document $project, array $queries, Database $database, callable $callback): void - { - $limit = 100; - $cursor = null; - - do { - $queries = \array_merge([ - Query::limit($limit), - Query::equal("projectInternalId", [$project->getInternalId()]) - ], $queries); - - if ($cursor !== null) { - $queries[] = Query::cursorAfter($cursor); - } - - $results = $database->find('rules', $queries); - - $total = \count($results); - if ($total > 0) { - $cursor = $results[$total - 1]; - } - - if ($total < $limit) { - $cursor = null; - } - - foreach ($results as $document) { - if (is_callable($callback)) { - $callback($document); - } - } - } while (!\is_null($cursor)); - } } diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php index 13b1e0c830..d78d43aec7 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php @@ -347,10 +347,16 @@ class Create extends Action 'domain' => $domain, 'type' => 'deployment', 'trigger' => 'deployment', - 'value' => $deployment->getId(), + 'deploymentId' => $deployment->isEmpty() ? '' : $deployment->getId(), + 'deploymentInternalId' => $deployment->isEmpty() ? '' : $deployment->getInternalId(), + 'deploymentResourceType' => 'site', + 'deploymentResourceId' => $site->getId(), + 'deploymentResourceInternalId' => $site->getInternalId(), 'status' => 'verified', 'certificateId' => '', 'search' => implode(' ', [$ruleId, $domain]), + 'owner' => 'Appwrite', + 'region' => $project->getAttribute('region') ])) ); } else { diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php index 7f1681c0f1..a54d0c7889 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Deployment/Update.php @@ -5,13 +5,14 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites\Deployment; use Appwrite\Event\Event; use Appwrite\Extend\Exception; use Appwrite\Platform\Modules\Compute\Base; -use Appwrite\Query; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; use Appwrite\SDK\Response as SDKResponse; use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\UID; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; @@ -86,8 +87,6 @@ class Update extends Base throw new Exception(Exception::BUILD_NOT_READY); } - $oldDeploymentInternalId = $site->getAttribute('deploymentInternalId', ''); - $site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [ 'deploymentInternalId' => $deployment->getInternalId(), 'deploymentId' => $deployment->getId(), @@ -97,24 +96,20 @@ class Update extends Base ]))); $queries = [ - Query::equal('trigger', 'manual'), + Query::equal('trigger', ['manual']), Query::equal("type", ["deployment"]), Query::equal("deploymentResourceType", ["site"]), Query::equal("deploymentResourceInternalId", [$site->getInternalId()]), + Query::equal("deploymentVcsProviderBranch", [""]), + Query::equal("projectInternalId", [$project->getInternalId()]) ]; - if (empty($oldDeploymentInternalId)) { - $queries[] = Query::equal("deploymentInternalId", [""]); - } else { - $queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]); - } - - $this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) { + Authorization::skip(fn () => $dbForPlatform->foreach('rules', function (Document $rule) use ($dbForPlatform, $deployment) { $rule = $rule ->setAttribute('deploymentId', $deployment->getId()) ->setAttribute('deploymentInternalId', $deployment->getInternalId()); - $dbForPlatform->updateDocument('rules', $rule->getId(), $rule); - }); + Authorization::skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), $rule)); + }, $queries)); $queueForEvents ->setParam('siteId', $site->getId()) 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) { 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); diff --git a/src/Appwrite/Utopia/Response/Model/AttributeString.php b/src/Appwrite/Utopia/Response/Model/AttributeString.php index fded48fddc..12bb42009d 100644 --- a/src/Appwrite/Utopia/Response/Model/AttributeString.php +++ b/src/Appwrite/Utopia/Response/Model/AttributeString.php @@ -24,13 +24,6 @@ class AttributeString extends Attribute 'required' => false, 'example' => 'default', ]) - ->addRule('encrypt', [ - 'type' => self::TYPE_BOOLEAN, - 'description' => 'Defines whether this attribute is encrypted or not.', - 'default' => false, - 'required' => false, - 'example' => false, - ]) ; } diff --git a/src/Appwrite/Vcs/Comment.php b/src/Appwrite/Vcs/Comment.php index 7c550ad528..d1d50c2ffd 100644 --- a/src/Appwrite/Vcs/Comment.php +++ b/src/Appwrite/Vcs/Comment.php @@ -103,10 +103,12 @@ class Comment $text .= "| :- | :- | :- | :- | :- |\n"; foreach ($project['site'] as $siteId => $site) { + $imageStatus = in_array($site['status'], ['processing', 'building']) ? 'building' : $site['status']; + $extension = $site['status'] === 'building' ? 'gif' : 'png'; - $pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension; - $pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension; + $pathLight = '/images/vcs/status-' . $imageStatus . '-light.' . $extension; + $pathDark = '/images/vcs/status-' . $imageStatus . '-dark.' . $extension; $status = match ($site['status']) { 'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_', @@ -149,10 +151,11 @@ class Comment $text .= "| :- | :- | :- | :- |\n"; foreach ($project['function'] as $functionId => $function) { - $extension = $function['status'] === 'building' ? 'gif' : 'png'; + $imageStatus = in_array($function['status'], ['processing', 'building']) ? 'building' : $function['status']; + $extension = $imageStatus === 'building' ? 'gif' : 'png'; - $pathLight = '/images/vcs/status-' . $function['status'] . '-light.' . $extension; - $pathDark = '/images/vcs/status-' . $function['status'] . '-dark.' . $extension; + $pathLight = '/images/vcs/status-' . $imageStatus . '-light.' . $extension; + $pathDark = '/images/vcs/status-' . $imageStatus . '-dark.' . $extension; $status = match ($function['status']) { 'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_', @@ -168,7 +171,8 @@ class Comment $action = '[Authorize](' . $function['action']['url'] . ')'; } - $text .= "|  **{$function['name']}**
`$functionId`"; + $text .= "|  **{$function['name']}**"; + $text .= "| `{$functionId}`"; $text .= "| {$status}"; $text .= "| {$action}"; $text .= "|\n"; diff --git a/tests/e2e/General/UsageTest.php b/tests/e2e/General/UsageTest.php index 5549ef800d..6ff9dafada 100644 --- a/tests/e2e/General/UsageTest.php +++ b/tests/e2e/General/UsageTest.php @@ -3,7 +3,6 @@ namespace Tests\E2E\General; use Appwrite\Platform\Modules\Compute\Specification; -use Appwrite\Tests\Retry; use CURLFile; use DateTime; use Tests\E2E\Client; @@ -183,40 +182,43 @@ class UsageTest extends Scope /** * @depends testPrepareUsersStats */ - #[Retry(count: 1)] public function testUsersStats(array $data): array { $requestsTotal = $data['requestsTotal']; - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1h', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); + $this->assertEventually(function () { + $response = $this->client->call( + Client::METHOD_GET, + '/project/usage', + $this->getConsoleHeaders(), + [ + 'period' => '1h', + 'startDate' => self::getToday(), + 'endDate' => self::getTomorrow(), + ] + ); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertGreaterThanOrEqual(31, count($response['body'])); - $this->validateDates($response['body']['network']); - $this->validateDates($response['body']['requests']); - $this->validateDates($response['body']['users']); - $this->assertArrayHasKey('executionsBreakdown', $response['body']); - $this->assertArrayHasKey('bucketsBreakdown', $response['body']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertGreaterThanOrEqual(31, count($response['body'])); + $this->validateDates($response['body']['network']); + $this->validateDates($response['body']['requests']); + $this->validateDates($response['body']['users']); + $this->assertArrayHasKey('executionsBreakdown', $response['body']); + $this->assertArrayHasKey('bucketsBreakdown', $response['body']); + }); - $response = $this->client->call( - Client::METHOD_GET, - '/users/usage?range=90d', - $this->getConsoleHeaders() - ); + $this->assertEventually(function () { + $response = $this->client->call( + Client::METHOD_GET, + '/users/usage?range=90d', + $this->getConsoleHeaders() + ); - $this->assertEquals('90d', $response['body']['range']); - $this->assertEquals(90, count($response['body']['users'])); - $this->assertEquals(90, count($response['body']['sessions'])); - $this->assertEquals((self::CREATE / 2), $response['body']['users'][array_key_last($response['body']['users'])]['value']); + $this->assertEquals('90d', $response['body']['range']); + $this->assertEquals(90, count($response['body']['users'])); + $this->assertEquals(90, count($response['body']['sessions'])); + $this->assertEquals((self::CREATE / 2), $response['body']['users'][array_key_last($response['body']['users'])]['value']); + }); return array_merge($data, [ 'requestsTotal' => $requestsTotal @@ -359,7 +361,6 @@ class UsageTest extends Scope /** * @depends testPrepareStorageStats */ - #[Retry(count: 10)] public function testStorageStats(array $data): array { $bucketId = $data['bucketId']; @@ -368,44 +369,50 @@ class UsageTest extends Scope $storageTotal = $data['storageTotal']; $filesTotal = $data['filesTotal']; - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1d', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); + $this->assertEventually(function () use ($requestsTotal, $storageTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/project/usage', + $this->getConsoleHeaders(), + [ + 'period' => '1d', + 'startDate' => self::getToday(), + 'endDate' => self::getTomorrow(), + ] + ); - $this->assertGreaterThanOrEqual(31, count($response['body'])); - $this->assertEquals(1, count($response['body']['requests'])); - $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); - $this->validateDates($response['body']['requests']); - $this->assertEquals($storageTotal, $response['body']['filesStorageTotal']); + $this->assertGreaterThanOrEqual(31, count($response['body'])); + $this->assertEquals(1, count($response['body']['requests'])); + $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); + $this->validateDates($response['body']['requests']); + $this->assertEquals($storageTotal, $response['body']['filesStorageTotal']); + }); - $response = $this->client->call( - Client::METHOD_GET, - '/storage/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEventually(function () use ($bucketsTotal, $filesTotal, $storageTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/storage/usage?range=30d', + $this->getConsoleHeaders() + ); - $this->assertEquals($storageTotal, $response['body']['storage'][array_key_last($response['body']['storage'])]['value']); - $this->validateDates($response['body']['storage']); - $this->assertEquals($bucketsTotal, $response['body']['buckets'][array_key_last($response['body']['buckets'])]['value']); - $this->validateDates($response['body']['buckets']); - $this->assertEquals($filesTotal, $response['body']['files'][array_key_last($response['body']['files'])]['value']); - $this->validateDates($response['body']['files']); + $this->assertEquals($storageTotal, $response['body']['storage'][array_key_last($response['body']['storage'])]['value']); + $this->validateDates($response['body']['storage']); + $this->assertEquals($bucketsTotal, $response['body']['buckets'][array_key_last($response['body']['buckets'])]['value']); + $this->validateDates($response['body']['buckets']); + $this->assertEquals($filesTotal, $response['body']['files'][array_key_last($response['body']['files'])]['value']); + $this->validateDates($response['body']['files']); + }); - $response = $this->client->call( - Client::METHOD_GET, - '/storage/' . $bucketId . '/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEventually(function () use ($bucketId, $storageTotal, $filesTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/storage/' . $bucketId . '/usage?range=30d', + $this->getConsoleHeaders() + ); - $this->assertEquals($storageTotal, $response['body']['storage'][array_key_last($response['body']['storage'])]['value']); - $this->assertEquals($filesTotal, $response['body']['files'][array_key_last($response['body']['files'])]['value']); + $this->assertEquals($storageTotal, $response['body']['storage'][array_key_last($response['body']['storage'])]['value']); + $this->assertEquals($filesTotal, $response['body']['files'][array_key_last($response['body']['files'])]['value']); + }); return $data; } @@ -577,7 +584,7 @@ class UsageTest extends Scope } /** @depends testPrepareDatabaseStats */ - #[Retry(count: 1)] + public function testDatabaseStats(array $data): array { $databaseId = $data['databaseId']; @@ -587,60 +594,66 @@ class UsageTest extends Scope $collectionsTotal = $data['collectionsTotal']; $documentsTotal = $data['documentsTotal']; - sleep(self::WAIT); + $this->assertEventually(function () use ($requestsTotal, $databasesTotal, $documentsTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/project/usage', + $this->getConsoleHeaders(), + [ + 'period' => '1d', + 'startDate' => self::getToday(), + 'endDate' => self::getTomorrow(), + ] + ); - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1d', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); + $this->assertGreaterThanOrEqual(31, count($response['body'])); + $this->assertEquals(1, count($response['body']['requests'])); + $this->assertEquals(1, count($response['body']['network'])); + $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); + $this->validateDates($response['body']['requests']); + $this->assertEquals($databasesTotal, $response['body']['databasesTotal']); + $this->assertEquals($documentsTotal, $response['body']['documentsTotal']); + }); - $this->assertGreaterThanOrEqual(31, count($response['body'])); - $this->assertEquals(1, count($response['body']['requests'])); - $this->assertEquals(1, count($response['body']['network'])); - $this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']); - $this->validateDates($response['body']['requests']); - $this->assertEquals($databasesTotal, $response['body']['databasesTotal']); - $this->assertEquals($documentsTotal, $response['body']['documentsTotal']); + $this->assertEventually(function () use ($collectionsTotal, $databasesTotal, $documentsTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/databases/usage?range=30d', + $this->getConsoleHeaders() + ); - $response = $this->client->call( - Client::METHOD_GET, - '/databases/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEquals($databasesTotal, $response['body']['databases'][array_key_last($response['body']['databases'])]['value']); + $this->validateDates($response['body']['databases']); + $this->assertEquals($collectionsTotal, $response['body']['collections'][array_key_last($response['body']['collections'])]['value']); + $this->validateDates($response['body']['collections']); + $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); + $this->validateDates($response['body']['documents']); + }); - $this->assertEquals($databasesTotal, $response['body']['databases'][array_key_last($response['body']['databases'])]['value']); - $this->validateDates($response['body']['databases']); - $this->assertEquals($collectionsTotal, $response['body']['collections'][array_key_last($response['body']['collections'])]['value']); - $this->validateDates($response['body']['collections']); - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); + $this->assertEventually(function () use ($databaseId, $collectionsTotal, $documentsTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/usage?range=30d', + $this->getConsoleHeaders() + ); - $response = $this->client->call( - Client::METHOD_GET, - '/databases/' . $databaseId . '/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEquals($collectionsTotal, $response['body']['collections'][array_key_last($response['body']['collections'])]['value']); + $this->validateDates($response['body']['collections']); - $this->assertEquals($collectionsTotal, $response['body']['collections'][array_key_last($response['body']['collections'])]['value']); - $this->validateDates($response['body']['collections']); + $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); + $this->validateDates($response['body']['documents']); + }); - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); + $this->assertEventually(function () use ($databaseId, $collectionId, $documentsTotal) { + $response = $this->client->call( + Client::METHOD_GET, + '/databases/' . $databaseId . '/collections/' . $collectionId . '/usage?range=30d', + $this->getConsoleHeaders() + ); - $response = $this->client->call( - Client::METHOD_GET, - '/databases/' . $databaseId . '/collections/' . $collectionId . '/usage?range=30d', - $this->getConsoleHeaders() - ); - - $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); - $this->validateDates($response['body']['documents']); + $this->assertEquals($documentsTotal, $response['body']['documents'][array_key_last($response['body']['documents'])]['value']); + $this->validateDates($response['body']['documents']); + }); return $data; } @@ -799,67 +812,69 @@ class UsageTest extends Scope } /** @depends testPrepareFunctionsStats */ - #[Retry(count: 1)] public function testFunctionsStats(array $data): array { $functionId = $data['functionId']; $executionTime = $data['executionTime']; $executions = $data['executions']; - $response = $this->client->call( - Client::METHOD_GET, - '/functions/' . $functionId . '/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEventually(function () use ($functionId, $executions, $executionTime) { + $response = $this->client->call( + Client::METHOD_GET, + '/functions/' . $functionId . '/usage?range=30d', + $this->getConsoleHeaders() + ); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(24, count($response['body'])); - $this->assertEquals('30d', $response['body']['range']); - $this->assertIsArray($response['body']['deployments']); - $this->assertIsArray($response['body']['deploymentsStorage']); - $this->assertIsNumeric($response['body']['deploymentsStorageTotal']); - $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']); - $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']); - $this->assertIsArray($response['body']['builds']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsMbSeconds']); - $this->assertIsArray($response['body']['executions']); - $this->assertIsArray($response['body']['executionsTime']); - $this->assertIsArray($response['body']['executionsMbSeconds']); - $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); - $this->validateDates($response['body']['executions']); - $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); - $this->validateDates($response['body']['executionsTime']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(24, count($response['body'])); + $this->assertEquals('30d', $response['body']['range']); + $this->assertIsArray($response['body']['deployments']); + $this->assertIsArray($response['body']['deploymentsStorage']); + $this->assertIsNumeric($response['body']['deploymentsStorageTotal']); + $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']); + $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']); + $this->assertIsArray($response['body']['builds']); + $this->assertIsArray($response['body']['buildsTime']); + $this->assertIsArray($response['body']['buildsMbSeconds']); + $this->assertIsArray($response['body']['executions']); + $this->assertIsArray($response['body']['executionsTime']); + $this->assertIsArray($response['body']['executionsMbSeconds']); + $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); + $this->validateDates($response['body']['executions']); + $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); + $this->validateDates($response['body']['executionsTime']); + }); - $response = $this->client->call( - Client::METHOD_GET, - '/functions/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEventually(function () use ($executions, $executionTime) { + $response = $this->client->call( + Client::METHOD_GET, + '/functions/usage?range=30d', + $this->getConsoleHeaders() + ); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(25, count($response['body'])); - $this->assertEquals($response['body']['range'], '30d'); - $this->assertIsArray($response['body']['functions']); - $this->assertIsArray($response['body']['deployments']); - $this->assertIsArray($response['body']['deploymentsStorage']); - $this->assertIsArray($response['body']['builds']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsMbSeconds']); - $this->assertIsArray($response['body']['executions']); - $this->assertIsArray($response['body']['executionsTime']); - $this->assertIsArray($response['body']['executionsMbSeconds']); - $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); - $this->validateDates($response['body']['executions']); - $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); - $this->validateDates($response['body']['executionsTime']); - $this->assertGreaterThan(0, $response['body']['buildsTime'][array_key_last($response['body']['buildsTime'])]['value']); - $this->validateDates($response['body']['buildsTime']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(25, count($response['body'])); + $this->assertEquals($response['body']['range'], '30d'); + $this->assertIsArray($response['body']['functions']); + $this->assertIsArray($response['body']['deployments']); + $this->assertIsArray($response['body']['deploymentsStorage']); + $this->assertIsArray($response['body']['builds']); + $this->assertIsArray($response['body']['buildsTime']); + $this->assertIsArray($response['body']['buildsMbSeconds']); + $this->assertIsArray($response['body']['executions']); + $this->assertIsArray($response['body']['executionsTime']); + $this->assertIsArray($response['body']['executionsMbSeconds']); + $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); + $this->validateDates($response['body']['executions']); + $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); + $this->validateDates($response['body']['executionsTime']); + $this->assertGreaterThan(0, $response['body']['buildsTime'][array_key_last($response['body']['buildsTime'])]['value']); + $this->validateDates($response['body']['buildsTime']); + }); return $data; } - public function testPrepareSitesStats(): array { $siteId = $this->setupSite([ @@ -927,7 +942,6 @@ class UsageTest extends Scope } /** @depends testPrepareSitesStats */ - #[Retry(count: 1)] public function testSitesStats(array $data) { $siteId = $data['siteId']; @@ -935,67 +949,72 @@ class UsageTest extends Scope $executions = $data['executions'] ?? 0; $deploymentsSuccess = $data['deploymentsSuccess']; $deploymentsFailed = $data['deploymentsFailed']; - $response = $this->client->call( - Client::METHOD_GET, - '/sites/' . $siteId . '/usage?range=30d', - $this->getConsoleHeaders() - ); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(30, count($response['body'])); - $this->assertEquals('30d', $response['body']['range']); - $this->assertIsArray($response['body']['deployments']); - $this->assertEquals($deploymentsSuccess, $response['body']['buildsSuccessTotal']); - $this->assertEquals($deploymentsFailed, $response['body']['buildsFailedTotal']); - $this->assertIsArray($response['body']['deploymentsStorage']); - $this->assertIsNumeric($response['body']['deploymentsStorageTotal']); - $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']); - $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']); - $this->assertIsArray($response['body']['builds']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsMbSeconds']); - $this->assertIsArray($response['body']['executions']); - $this->assertIsArray($response['body']['executionsTime']); - $this->assertIsArray($response['body']['executionsMbSeconds']); - $this->assertIsArray($response['body']['buildsSuccess']); - $this->assertIsArray($response['body']['buildsFailed']); - $this->assertIsArray($response['body']['requests']); - $this->assertIsArray($response['body']['inbound']); - $this->assertIsArray($response['body']['outbound']); - $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); - $this->validateDates($response['body']['executions']); - $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); - $this->validateDates($response['body']['executionsTime']); + $this->assertEventually(function () use ($siteId, $deploymentsSuccess, $deploymentsFailed, $executions, $executionTime) { + $response = $this->client->call( + Client::METHOD_GET, + '/sites/' . $siteId . '/usage?range=30d', + $this->getConsoleHeaders() + ); - $response = $this->client->call( - Client::METHOD_GET, - '/sites/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(30, count($response['body'])); + $this->assertEquals('30d', $response['body']['range']); + $this->assertIsArray($response['body']['deployments']); + $this->assertEquals($deploymentsSuccess, $response['body']['buildsSuccessTotal']); + $this->assertEquals($deploymentsFailed, $response['body']['buildsFailedTotal']); + $this->assertIsArray($response['body']['deploymentsStorage']); + $this->assertIsNumeric($response['body']['deploymentsStorageTotal']); + $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']); + $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']); + $this->assertIsArray($response['body']['builds']); + $this->assertIsArray($response['body']['buildsTime']); + $this->assertIsArray($response['body']['buildsMbSeconds']); + $this->assertIsArray($response['body']['executions']); + $this->assertIsArray($response['body']['executionsTime']); + $this->assertIsArray($response['body']['executionsMbSeconds']); + $this->assertIsArray($response['body']['buildsSuccess']); + $this->assertIsArray($response['body']['buildsFailed']); + $this->assertIsArray($response['body']['requests']); + $this->assertIsArray($response['body']['inbound']); + $this->assertIsArray($response['body']['outbound']); + $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); + $this->validateDates($response['body']['executions']); + $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); + $this->validateDates($response['body']['executionsTime']); + }); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(31, count($response['body'])); - $this->assertEquals($response['body']['range'], '30d'); - $this->assertIsArray($response['body']['sites']); - $this->assertIsArray($response['body']['deployments']); - $this->assertIsArray($response['body']['deploymentsStorage']); - $this->assertIsArray($response['body']['builds']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsMbSeconds']); - $this->assertIsArray($response['body']['executions']); - $this->assertIsArray($response['body']['executionsTime']); - $this->assertIsArray($response['body']['executionsMbSeconds']); - $this->assertIsArray($response['body']['buildsSuccess']); - $this->assertIsArray($response['body']['buildsFailed']); - $this->assertIsArray($response['body']['requests']); - $this->assertIsArray($response['body']['inbound']); - $this->assertIsArray($response['body']['outbound']); - $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); - $this->validateDates($response['body']['executions']); - $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); - $this->validateDates($response['body']['executionsTime']); - $this->assertGreaterThan(0, $response['body']['buildsTime'][array_key_last($response['body']['buildsTime'])]['value']); - $this->validateDates($response['body']['buildsTime']); + $this->assertEventually(function () use ($executions, $executionTime) { + $response = $this->client->call( + Client::METHOD_GET, + '/sites/usage?range=30d', + $this->getConsoleHeaders() + ); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(31, count($response['body'])); + $this->assertEquals($response['body']['range'], '30d'); + $this->assertIsArray($response['body']['sites']); + $this->assertIsArray($response['body']['deployments']); + $this->assertIsArray($response['body']['deploymentsStorage']); + $this->assertIsArray($response['body']['builds']); + $this->assertIsArray($response['body']['buildsTime']); + $this->assertIsArray($response['body']['buildsMbSeconds']); + $this->assertIsArray($response['body']['executions']); + $this->assertIsArray($response['body']['executionsTime']); + $this->assertIsArray($response['body']['executionsMbSeconds']); + $this->assertIsArray($response['body']['buildsSuccess']); + $this->assertIsArray($response['body']['buildsFailed']); + $this->assertIsArray($response['body']['requests']); + $this->assertIsArray($response['body']['inbound']); + $this->assertIsArray($response['body']['outbound']); + $this->assertEquals($executions, $response['body']['executions'][array_key_last($response['body']['executions'])]['value']); + $this->validateDates($response['body']['executions']); + $this->assertEquals($executionTime, $response['body']['executionsTime'][array_key_last($response['body']['executionsTime'])]['value']); + $this->validateDates($response['body']['executionsTime']); + $this->assertGreaterThan(0, $response['body']['buildsTime'][array_key_last($response['body']['buildsTime'])]['value']); + $this->validateDates($response['body']['buildsTime']); + }); } /** @depends testFunctionsStats */ @@ -1032,30 +1051,33 @@ class UsageTest extends Scope $domain = $rule['body']['domain']; - $response = $this->client->call( - Client::METHOD_GET, - '/functions/' . $functionId . '/usage?range=30d', - $this->getConsoleHeaders() - ); + $this->assertEventually(function () use (&$response, $functionId) { + $response = $this->client->call( + Client::METHOD_GET, + '/functions/' . $functionId . '/usage?range=30d', + $this->getConsoleHeaders() + ); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(24, count($response['body'])); - $this->assertEquals('30d', $response['body']['range']); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(24, count($response['body'])); + $this->assertEquals('30d', $response['body']['range']); + }); $functionsMetrics = $response['body']; - $response = $this->client->call( - Client::METHOD_GET, - '/project/usage', - $this->getConsoleHeaders(), - [ - 'period' => '1h', - 'startDate' => self::getToday(), - 'endDate' => self::getTomorrow(), - ] - ); - - $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEventually(function () use (&$response) { + $response = $this->client->call( + Client::METHOD_GET, + '/project/usage', + $this->getConsoleHeaders(), + [ + 'period' => '1h', + 'startDate' => self::getToday(), + 'endDate' => self::getTomorrow(), + ] + ); + $this->assertEquals(200, $response['headers']['status-code']); + }); $projectMetrics = $response['body']; @@ -1070,8 +1092,6 @@ class UsageTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $tries = 0; - $this->assertEventually(function () use ($functionId, $functionsMetrics, $projectMetrics) { // Compare new values with old values $response = $this->client->call( diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 6deaa62c05..2fa6416bf7 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Scopes; +use Appwrite\Tests\Async; use Appwrite\Tests\Retryable; use PHPUnit\Framework\TestCase; use Tests\E2E\Client; @@ -10,6 +11,7 @@ use Utopia\Database\Helpers\ID; abstract class Scope extends TestCase { use Retryable; + use Async; protected ?Client $client = null; protected string $endpoint = 'http://localhost/v1'; @@ -43,6 +45,18 @@ abstract class Scope extends TestCase return []; } + protected function assertLastRequest(callable $probe, $timeoutMs = 20_000, $waitMs = 500): array + { + $this->assertEventually(function () use (&$request, $probe) { + $request = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true); + $request['data'] = json_decode($request['data'], true); + + call_user_func($probe, $request); + }, $timeoutMs, $waitMs); + + return $request; + } + protected function getLastRequest(): array { sleep(2); diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 452b725c11..0cc2eb893a 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2034,7 +2034,6 @@ class AccountCustomClientTest extends Scope $this->assertEquals($response['body']['users'][0]['email'], $email); } - #[Retry(count: 2)] public function testCreatePhone(): array { $number = '+123456789'; @@ -2058,17 +2057,15 @@ class AccountCustomClientTest extends Scope $userId = $response['body']['userId']; - \sleep(7); - - $smsRequest = $this->getLastRequest(); - - $this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']); - $this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']); - $this->assertEquals('username', $smsRequest['headers']['X-Username']); - $this->assertEquals('password', $smsRequest['headers']['X-Key']); - $this->assertEquals('POST', $smsRequest['method']); - $this->assertEquals('+123456789', $smsRequest['data']['from']); - $this->assertEquals($number, $smsRequest['data']['to']); + $smsRequest = $this->assertLastRequest(function (array $request) use ($number) { + $this->assertEquals('http://request-catcher:5000/mock-sms', $request['url']); + $this->assertEquals('Appwrite Mock Message Sender', $request['headers']['User-Agent']); + $this->assertEquals('username', $request['headers']['X-Username']); + $this->assertEquals('password', $request['headers']['X-Key']); + $this->assertEquals('POST', $request['method']); + $this->assertEquals('+123456789', $request['data']['from']); + $this->assertEquals($number, $request['data']['to']); + }); $data['token'] = $smsRequest['data']['message']; $data['id'] = $userId; @@ -2396,7 +2393,6 @@ class AccountCustomClientTest extends Scope /** * @depends testUpdatePhone */ - #[Retry(count: 3)] public function testPhoneVerification(array $data): array { $session = $data['session'] ?? ''; @@ -2416,10 +2412,10 @@ class AccountCustomClientTest extends Scope $this->assertEmpty($response['body']['secret']); $this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire'])); - $smsRequest = $this->getLastRequest(); - - $message = $smsRequest['data']['message']; - $token = substr($message, 0, 6); + $smsRequest = $this->assertLastRequest(function ($request) { + $this->assertArrayHasKey('data', $request); + $this->assertArrayHasKey('message', $request['data']); + }); /** * Test for FAILURE diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 9aed3684de..7c0060ecaa 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -298,7 +298,7 @@ trait DatabasesBase $this->assertEquals($title['body']['type'], 'string'); $this->assertEquals($title['body']['size'], 256); $this->assertEquals($title['body']['required'], true); - $this->assertFalse($title['body']['encrypt']); + $this->assertEquals(202, $description['headers']['status-code']); $this->assertEquals($description['body']['key'], 'description'); $this->assertEquals($description['body']['type'], 'string'); diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index e66207b215..829960b54f 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -686,18 +686,6 @@ class DatabasesCustomServerTest extends Scope 'size' => 256, 'required' => true, ]); - // checking size test - $lastName = $this->client->call(Client::METHOD_POST, $attributesPath . '/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'lastName', - 'size' => 149, - 'required' => true, - 'encrypt' => true - ]); - $this->assertEquals("Size too small. Encrypted strings require a minimum size of " . APP_DATABASE_ENCRYPT_SIZE_MIN . " characters.", $lastName['body']['message']); $lastName = $this->client->call(Client::METHOD_POST, $attributesPath . '/string', array_merge([ 'content-type' => 'application/json', @@ -707,16 +695,9 @@ class DatabasesCustomServerTest extends Scope 'key' => 'lastName', 'size' => 256, 'required' => true, - 'encrypt' => true + 'encrypt' => true, ]); - $this->assertTrue($lastName['body']['encrypt']); - sleep(1); - $response = $this->client->call(Client::METHOD_GET, $attributesPath . '/lastName', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ])); - $this->assertTrue($response['body']['encrypt']); + /** * Check status of every attribute @@ -760,24 +741,6 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(200, $document['headers']['status-code']); $this->assertEquals('Jonah', $document['body']['firstName']); $this->assertEquals('Jameson', $document['body']['lastName']); - - - $actors = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $actors['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), []); - $attributes = $actors['body']['attributes']; - foreach ($attributes as $attribute) { - $this->assertArrayHasKey('encrypt', $attribute); - if ($attribute['key'] === 'firstName') { - $this->assertFalse($attribute['encrypt']); - } - if ($attribute['key'] === 'lastName') { - $this->assertTrue($attribute['encrypt']); - } - } - } public function testDeleteAttribute(): array diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 475eb9b9b4..27b67d851d 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -49,7 +49,7 @@ trait FunctionsBase 'x-appwrite-key' => $this->getProject()['apiKey'], ])); $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - }, 50000, 500); + }, 100000, 500); // Not === so multipart/form-data works fine too if (($params['activate'] ?? false) == true) { diff --git a/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php b/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php index 245eb3e8de..49d13128e2 100644 --- a/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php +++ b/tests/e2e/Services/Messaging/MessagingConsoleClientTest.php @@ -206,15 +206,17 @@ class MessagingConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); - $logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $this->assertEventually(function () use ($topic) { + $logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); - $this->assertEquals($logs['headers']['status-code'], 200); - $this->assertIsArray($logs['body']['logs']); - $this->assertCount(2, $logs['body']['logs']); - $this->assertIsNumeric($logs['body']['total']); + $this->assertEquals($logs['headers']['status-code'], 200); + $this->assertIsArray($logs['body']['logs']); + $this->assertCount(2, $logs['body']['logs']); + $this->assertIsNumeric($logs['body']['total']); + }); $logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index c241b38e3d..b8b9439e64 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -871,7 +871,7 @@ trait MigrationsBase $this->assertEquals(1, $deployments['body']['total']); $this->assertEquals('ready', $deployments['body']['deployments'][0]['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployments['body']['deployments'][0], JSON_PRETTY_PRINT)); - }, 50000, 500); + }, 100000, 500); // Attempt execution $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [ diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 00edcc1b72..2054744863 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -265,7 +265,7 @@ trait SitesBase $this->assertEventually(function () use ($siteId, $deploymentId) { $deployment = $this->getDeployment($siteId, $deploymentId); $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); - }, 100000, 500); + }, 150000, 500); $this->assertEventually(function () use ($siteId, $deploymentId) { $site = $this->getSite($siteId); diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index d3b4922b87..8459e46c6f 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -1446,7 +1446,7 @@ class SitesCustomServerTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); $this->assertStringContainsString("Page not found", $response['body']); // Title $this->assertStringContainsString("Go to homepage", $response['body']); // Button - $this->assertStringContainsString("Powered by", $response['body']); // Brand + $this->assertStringNotContainsString("Powered by", $response['body']); // Brand $this->cleanupSite($siteId); } @@ -1502,7 +1502,7 @@ class SitesCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertStringContainsString("Customized 404 page", $response['body']); - $this->assertStringNotContainsString("Powered by", $response['body']); // Brand + $this->assertStringNotContainsString("Powered by", $response['body']); //brand $this->cleanupSite($siteId); } 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 f096831a76..7949761d7f 100644 --- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php +++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php @@ -65,7 +65,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()), [ @@ -75,7 +75,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', @@ -90,13 +91,26 @@ 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', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $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. + // 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; @@ -109,21 +123,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 */ @@ -132,7 +131,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']); @@ -145,7 +144,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']); @@ -160,7 +159,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']); @@ -168,4 +167,89 @@ 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']); + + $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(204, $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(2, $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('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([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + + return []; + } }