diff --git a/app/cli.php b/app/cli.php index 7c2daf4500..119534a8b9 100644 --- a/app/cli.php +++ b/app/cli.php @@ -251,7 +251,7 @@ CLI::setResource('logError', function (Registry $register) { }; }, ['register']); -CLI::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); +CLI::setResource('executor', fn () => new Executor()); CLI::setResource('telemetry', fn () => new NoTelemetry()); diff --git a/app/config/platforms.php b/app/config/platforms.php index 7d481d508e..f08401e8fa 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -59,7 +59,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '16.1.0', + 'version' => '17.0.1', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -77,7 +77,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '10.1.0', + 'version' => '10.1.1', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -217,7 +217,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '7.0.0', + 'version' => '8.0.0', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, @@ -231,6 +231,11 @@ return [ 'gitRepoName' => 'sdk-for-cli', 'gitUserName' => 'appwrite', 'gitBranch' => 'dev', + 'exclude' => [ + 'services' => [ + ['name' => 'assistant'], + ], + ], ], ], ], @@ -411,7 +416,7 @@ return [ [ 'key' => 'swift', 'name' => 'Swift', - 'version' => '10.0.0', + 'version' => '10.1.0', 'url' => 'https://github.com/appwrite/sdk-for-swift', 'package' => 'https://github.com/appwrite/sdk-for-swift', 'enabled' => true, diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index bbb78c6c66..807903b764 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -4490,6 +4490,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": { @@ -6595,7 +6618,8 @@ "png", "webp", "heic", - "avif" + "avif", + "gif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index 90ef137fc2..cc4753dd4a 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -9767,6 +9767,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -9792,7 +9793,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -10395,6 +10397,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -10420,7 +10423,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -25446,12 +25450,33 @@ "Temporary Redirect 307", "Permanent Redirect 308" ] + }, + "resourceId": { + "type": "string", + "description": "ID of parent resource.", + "x-example": "" + }, + "resourceType": { + "type": "string", + "description": "Type of parent resource.", + "x-example": "site", + "enum": [ + "site", + "function" + ], + "x-enum-name": "ProxyResourceType", + "x-enum-keys": [ + "Site", + "Function" + ] } }, "required": [ "domain", "url", - "statusCode" + "statusCode", + "resourceId", + "resourceType" ] } } @@ -25936,6 +25961,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -25961,7 +25987,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -26580,6 +26607,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -26605,7 +26633,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -29364,7 +29393,8 @@ "png", "webp", "heic", - "avif" + "avif", + "gif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -34846,6 +34876,17 @@ "default": "" }, "in": "query" + }, + { + "name": "providerReference", + "description": "Git reference (branch, tag, commit) to get contents from", + "required": false, + "schema": { + "type": "string", + "x-example": "", + "default": "" + }, + "in": "query" } ] } diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 1ae9328864..cc0abd241d 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -8844,6 +8844,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -8869,7 +8870,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -9244,6 +9246,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -9269,7 +9272,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -17490,6 +17494,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -17515,7 +17520,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -17906,6 +17912,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -17931,7 +17938,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -20646,7 +20654,8 @@ "png", "webp", "heic", - "avif" + "avif", + "gif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 92132151b4..b3f41e101c 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -4636,6 +4636,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": { @@ -6729,7 +6752,8 @@ "png", "webp", "heic", - "avif" + "avif", + "gif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index e53a0dfb0b..4e60716c4b 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -9835,6 +9835,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -9860,7 +9861,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -10466,6 +10468,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -10491,7 +10494,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -25696,12 +25700,35 @@ "Temporary Redirect 307", "Permanent Redirect 308" ] + }, + "resourceId": { + "type": "string", + "description": "ID of parent resource.", + "default": null, + "x-example": "" + }, + "resourceType": { + "type": "string", + "description": "Type of parent resource.", + "default": null, + "x-example": "site", + "enum": [ + "site", + "function" + ], + "x-enum-name": "ProxyResourceType", + "x-enum-keys": [ + "Site", + "Function" + ] } }, "required": [ "domain", "url", - "statusCode" + "statusCode", + "resourceId", + "resourceType" ] } } @@ -26203,6 +26230,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -26228,7 +26256,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -26850,6 +26879,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -26875,7 +26905,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -29604,7 +29635,8 @@ "png", "webp", "heic", - "avif" + "avif", + "gif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], @@ -35063,6 +35095,15 @@ "x-example": "", "default": "", "in": "query" + }, + { + "name": "providerReference", + "description": "Git reference (branch, tag, commit) to get contents from", + "required": false, + "type": "string", + "x-example": "", + "default": "", + "in": "query" } ] } diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 083290bcc0..4c7c193858 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -8927,6 +8927,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -8952,7 +8953,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -9340,6 +9342,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -9365,7 +9368,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -17792,6 +17796,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -17817,7 +17822,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -18221,6 +18227,7 @@ "dart-3.1", "dart-3.3", "dart-3.5", + "dart-3.8", "dotnet-6.0", "dotnet-7.0", "dotnet-8.0", @@ -18246,7 +18253,8 @@ "static-1", "flutter-3.24", "flutter-3.27", - "flutter-3.29" + "flutter-3.29", + "flutter-3.32" ], "x-enum-name": null, "x-enum-keys": [] @@ -20935,7 +20943,8 @@ "png", "webp", "heic", - "avif" + "avif", + "gif" ], "x-enum-name": "ImageFormat", "x-enum-keys": [], diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index b95eb432a1..2bdaea3c2c 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -845,15 +845,18 @@ App::get('/v1/health/storage') $checkStart = \microtime(true); foreach ($devices as $device) { - if (!$device->write($device->getPath('health.txt'), 'test', 'text/plain')) { + $uniqueFileName = \uniqid('health', true); + $filePath = $device->getPath($uniqueFileName); + + if (!$device->write($filePath, 'test', 'text/plain')) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed writing test file to ' . $device->getRoot()); } - if ($device->read($device->getPath('health.txt')) !== 'test') { + if ($device->read($filePath) !== 'test') { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed reading test file from ' . $device->getRoot()); } - if (!$device->delete($device->getPath('health.txt'))) { + if (!$device->delete($filePath)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed deleting test file from ' . $device->getRoot()); } } diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index c8a0a36a85..7fb8713e25 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -605,11 +605,12 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro ->param('installationId', '', new Text(256), 'Installation Id') ->param('providerRepositoryId', '', new Text(256), 'Repository Id') ->param('providerRootDirectory', '', new Text(256, 0), 'Path to get contents of nested directory', true) + ->param('providerReference', '', new Text(256, 0), 'Git reference (branch, tag, commit) to get contents from', true) ->inject('gitHub') ->inject('response') ->inject('project') ->inject('dbForPlatform') - ->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { + ->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, string $providerReference, GitHub $github, Response $response, Document $project, Database $dbForPlatform) { $installation = $dbForPlatform->getDocument('installations', $installationId); if ($installation->isEmpty()) { @@ -631,7 +632,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND); } - $contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory); + $contents = $github->listRepositoryContents($owner, $repositoryName, $providerRootDirectory, $providerReference); $vcsContents = []; foreach ($contents as $content) { diff --git a/app/init/resources.php b/app/init/resources.php index 6ca0522b6c..39bdd66a86 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -880,7 +880,7 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key return Key::decode($project, $key); }, ['request', 'project']); -App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); +App::setResource('executor', fn () => new Executor()); App::setResource('resourceToken', function ($project, $dbForProject, $request) { $tokenJWT = $request->getParam('token'); diff --git a/app/worker.php b/app/worker.php index c25608cd5e..b596e8bd1b 100644 --- a/app/worker.php +++ b/app/worker.php @@ -402,7 +402,7 @@ Server::setResource('logError', function (Registry $register, Document $project) }; }, ['register', 'project']); -Server::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST'))); +Server::setResource('executor', fn () => new Executor()); $pools = $register->get('pools'); $platform = new Appwrite(); diff --git a/composer.lock b/composer.lock index d7b9f22722..fdde3f2750 100644 --- a/composer.lock +++ b/composer.lock @@ -4584,16 +4584,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.10.4", + "version": "0.10.5", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "f635b368909eb3c3fe57344fe43525e74e8fdc03" + "reference": "b358439dc387f6097019eb83ebb9fc258fe9da05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/f635b368909eb3c3fe57344fe43525e74e8fdc03", - "reference": "f635b368909eb3c3fe57344fe43525e74e8fdc03", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/b358439dc387f6097019eb83ebb9fc258fe9da05", + "reference": "b358439dc387f6097019eb83ebb9fc258fe9da05", "shasum": "" }, "require": { @@ -4627,9 +4627,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.10.4" + "source": "https://github.com/utopia-php/vcs/tree/0.10.5" }, - "time": "2025-06-02T09:18:36+00:00" + "time": "2025-06-10T15:01:16+00:00" }, { "name": "utopia-php/websocket", @@ -4807,16 +4807,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.41.1", + "version": "0.41.4", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "6d9318abf4542a757c87abf056557d6afa1dc06b" + "reference": "07804269131f411576aac60c795a5ebc3afaa48a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6d9318abf4542a757c87abf056557d6afa1dc06b", - "reference": "6d9318abf4542a757c87abf056557d6afa1dc06b", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/07804269131f411576aac60c795a5ebc3afaa48a", + "reference": "07804269131f411576aac60c795a5ebc3afaa48a", "shasum": "" }, "require": { @@ -4852,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.41.1" + "source": "https://github.com/appwrite/sdk-generator/tree/0.41.4" }, - "time": "2025-06-01T04:20:04+00:00" + "time": "2025-06-10T08:28:11+00:00" }, { "name": "doctrine/annotations", diff --git a/docker-compose.yml b/docker-compose.yml index 29a43aca91..c042d36cd5 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.32 + image: appwrite/console:6.0.41 restart: unless-stopped networks: - appwrite diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Download/Get.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Download/Get.php index 9c338d571a..fd22248fa3 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Download/Get.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Download/Get.php @@ -105,9 +105,11 @@ class Get extends Action $response ->setContentType('application/gzip') - ->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days + ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + ->addHeader('Expires', '0') + ->addHeader('Pragma', 'no-cache') ->addHeader('X-Peak', \memory_get_peak_usage()) - ->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"'); + ->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '-' . $type . '.tar.gz"'); $size = $device->getFileSize($path); $rangeHeader = $request->getHeader('range'); diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index f1b24f601d..afad3fb40e 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -877,6 +877,10 @@ class Builds extends Action } } + $deployment->setAttribute('buildLogs', $logs); + + $this->afterBuildSuccess($dbForProject, $deployment); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); $queueForRealtime @@ -1313,6 +1317,19 @@ class Builds extends Action ->trigger(); } + /** + * Hook to run after build success + * + * @param Database $dbForProject + * @param Document $deployment + * @return void + */ + protected function afterBuildSuccess(Database $dbForProject, Document &$deployment): void + { + assert($dbForProject instanceof Database); + assert($deployment instanceof Document); + } + protected function getRuntime(Document $resource, string $version): array { $runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []); diff --git a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php index 1cb671a76a..dbc0baa046 100644 --- a/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php +++ b/src/Appwrite/Platform/Modules/Proxy/Http/Rules/Redirect/Create.php @@ -14,6 +14,7 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; +use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; @@ -65,15 +66,18 @@ class Create extends Action ->param('domain', null, new ValidatorDomain(), 'Domain name.') ->param('url', null, new URL(), 'Target URL of redirection') ->param('statusCode', null, new WhiteList([301, 302, 307, 308]), 'Status code of redirection') + ->param('resourceId', '', new UID(), 'ID of parent resource.') + ->param('resourceType', '', new WhiteList(['site', 'function']), 'Type of parent resource.') ->inject('response') ->inject('project') ->inject('queueForCertificates') ->inject('queueForEvents') ->inject('dbForPlatform') + ->inject('dbForProject') ->callback($this->action(...)); } - public function action(string $domain, string $url, int $statusCode, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform) + public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { $deniedDomains = [ 'localhost', @@ -116,6 +120,15 @@ class Create extends Action throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.'); } + $collection = match ($resourceType) { + 'site' => 'sites', + 'function' => 'functions' + }; + $resource = $dbForProject->getDocument($collection, $resourceId); + if ($resource->isEmpty()) { + throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND); + } + // TODO: @christyjacob remove once we migrate the rules in 1.7.x $ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(); @@ -164,6 +177,9 @@ class Create extends Action 'trigger' => 'manual', 'redirectUrl' => $url, 'redirectStatusCode' => $statusCode, + 'deploymentResourceType' => $resourceType, + 'deploymentResourceId' => $resource->getId(), + 'deploymentResourceInternalId' => $resource->getInternalId(), 'certificateId' => '', 'search' => implode(' ', [$ruleId, $domain->get()]), 'owner' => $owner, diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Download/Get.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Download/Get.php index 9c05f3f3fd..5a87ce453f 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Download/Get.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Download/Get.php @@ -99,12 +99,14 @@ class Get extends Action } if (!$device->exists($path)) { - throw new Exception(Exception::BUILD_NOT_FOUND); + throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); } $response ->setContentType('application/gzip') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + ->addHeader('Expires', '0') + ->addHeader('Pragma', 'no-cache') ->addHeader('X-Peak', \memory_get_peak_usage()) ->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '-' . $type . '.tar.gz"'); diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php index d3776129e7..e6def793b5 100644 --- a/src/Appwrite/Platform/Tasks/Maintenance.php +++ b/src/Appwrite/Platform/Tasks/Maintenance.php @@ -4,9 +4,11 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Certificate; use Appwrite\Event\Delete; +use DateInterval; +use DateTime; use Utopia\CLI\Console; use Utopia\Database\Database; -use Utopia\Database\DateTime; +use Utopia\Database\DateTime as DatabaseDateTime; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Platform\Action; @@ -58,29 +60,35 @@ class Maintenance extends Action Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.'); Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) { - $time = DateTime::now(); + $time = DatabaseDateTime::now(); Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds"); + // Iterate through project only if it was accessed in last 30 days + $dateInterval = DateInterval::createFromDateString('30 days'); + $before30days = (new DateTime())->sub($dateInterval); + $dbForPlatform->foreach( 'projects', function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) { $queueForDeletes ->setType(DELETE_TYPE_MAINTENANCE) ->setProject($project) - ->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) + ->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) ->trigger(); }, [ Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), Query::limit(100), + Query::greaterThanEqual('accessedAt', DatabaseDateTime::format($before30days)), + Query::orderAsc('teamInternalId'), ] ); $queueForDeletes ->setType(DELETE_TYPE_MAINTENANCE) ->setProject($console) - ->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) + ->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) ->trigger(); $this->notifyDeleteConnections($queueForDeletes); @@ -94,13 +102,13 @@ class Maintenance extends Action { $queueForDeletes ->setType(DELETE_TYPE_REALTIME) - ->setDatetime(DateTime::addSeconds(new \DateTime(), -60)) + ->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -60)) ->trigger(); } private function renewCertificates(Database $dbForPlatform, Certificate $queueForCertificate): void { - $time = DateTime::now(); + $time = DatabaseDateTime::now(); $certificates = $dbForPlatform->find('certificates', [ Query::lessThan('attempts', 5), // Maximum 5 attempts @@ -129,7 +137,7 @@ class Maintenance extends Action { $queueForDeletes ->setType(DELETE_TYPE_CACHE_BY_TIMESTAMP) - ->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval)) + ->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $interval)) ->trigger(); } @@ -137,7 +145,7 @@ class Maintenance extends Action { $queueForDeletes ->setType(DELETE_TYPE_SCHEDULES) - ->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval)) + ->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $interval)) ->trigger(); } } diff --git a/src/Appwrite/Platform/Tasks/SDKs.php b/src/Appwrite/Platform/Tasks/SDKs.php index 2d3fad383e..a2e45cdf93 100644 --- a/src/Appwrite/Platform/Tasks/SDKs.php +++ b/src/Appwrite/Platform/Tasks/SDKs.php @@ -251,7 +251,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD) ->setDefaultHeaders([ 'X-Appwrite-Response-Format' => '1.7.0', - ]); + ]) + ->setExclude($language['exclude'] ?? []); // Make sure we have a clean slate. // Otherwise, all files in this dir will be pushed, diff --git a/src/Appwrite/Platform/Workers/Audits.php b/src/Appwrite/Platform/Workers/Audits.php index aadb58f971..4bd1393953 100644 --- a/src/Appwrite/Platform/Workers/Audits.php +++ b/src/Appwrite/Platform/Workers/Audits.php @@ -12,13 +12,13 @@ use Utopia\Database\Exception\Authorization; use Utopia\Database\Exception\Structure; use Utopia\Platform\Action; use Utopia\Queue\Message; +use Utopia\Queue\Result\Commit; +use Utopia\Queue\Result\NoCommit; use Utopia\System\System; class Audits extends Action { - protected const BATCH_SIZE_DEVELOPMENT = 1; // smaller batch size for development - protected const BATCH_SIZE_PRODUCTION = 5_000; - protected const BATCH_AGGREGATION_INTERVAL = 60; // in seconds + protected const int BATCH_AGGREGATION_INTERVAL = 60; // in seconds private int $lastTriggeredTime = 0; @@ -27,9 +27,7 @@ class Audits extends Action protected function getBatchSize(): int { - return System::getEnv('_APP_ENV', 'development') === 'development' - ? self::BATCH_SIZE_DEVELOPMENT - : self::BATCH_SIZE_PRODUCTION; + return intval(System::getEnv('_APP_QUEUE_PREFETCH_COUNT', 1)); } public static function getName(): string @@ -57,13 +55,13 @@ class Audits extends Action * @param Message $message * @param callable $getProjectDB * @param Document $project - * @return void + * @return Commit|NoCommit * @throws Throwable * @throws \Utopia\Database\Exception * @throws Authorization * @throws Structure */ - public function action(Message $message, callable $getProjectDB, Document $project): void + public function action(Message $message, callable $getProjectDB, Document $project): Commit|NoCommit { $payload = $message->getPayload() ?? []; @@ -123,29 +121,32 @@ class Audits extends Action // Check if we should process the batch by checking both for the batch size and the elapsed time $batchSize = $this->getBatchSize(); - $shouldProcessBatch = \count($this->logs) >= $batchSize; - if (!$shouldProcessBatch && \count($this->logs) > 0) { + $logCount = array_reduce($this->logs, fn (int $current, $logs) => $current + count($logs['logs']), 0); + $shouldProcessBatch = $logCount >= $batchSize; + if (!$shouldProcessBatch && $logCount > 0) { $shouldProcessBatch = (\time() - $this->lastTriggeredTime) >= self::BATCH_AGGREGATION_INTERVAL; } - if ($shouldProcessBatch) { - try { - foreach ($this->logs as $internalId => $projectLogs) { - $dbForProject = $getProjectDB($projectLogs['project']); - - Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events'); - $audit = new Audit($dbForProject); - - $audit->logBatch($projectLogs['logs']); - Console::success('Audit logs processed successfully'); - - unset($this->logs[$internalId]); - } - } catch (Throwable $e) { - Console::error('Error processing audit logs: ' . $e->getMessage()); - } finally { - $this->lastTriggeredTime = time(); - } + if (!$shouldProcessBatch) { + return new NoCommit(); } + + try { + foreach ($this->logs as $internalId => $projectLogs) { + $dbForProject = $getProjectDB($projectLogs['project']); + + Console::log('Processing batch with ' . count($projectLogs['logs']) . ' events'); + $audit = new Audit($dbForProject); + + $audit->logBatch($projectLogs['logs']); + Console::success('Audit logs processed successfully'); + + unset($this->logs[$internalId]); + } + } catch (Throwable $e) { + Console::error('Error processing audit logs: ' . $e->getMessage()); + } + $this->lastTriggeredTime = time(); + return new Commit(); } } diff --git a/src/Appwrite/Platform/Workers/StatsUsageDump.php b/src/Appwrite/Platform/Workers/StatsUsageDump.php deleted file mode 100644 index e1b25b0f04..0000000000 --- a/src/Appwrite/Platform/Workers/StatsUsageDump.php +++ /dev/null @@ -1,206 +0,0 @@ - true, - METRIC_BUCKETS => true, - METRIC_USERS => true, - METRIC_FUNCTIONS => true, - METRIC_TEAMS => true, - METRIC_MESSAGES => true, - METRIC_MAU => true, - METRIC_WEBHOOKS => true, - METRIC_PLATFORMS => true, - METRIC_PROVIDERS => true, - METRIC_TOPICS => true, - METRIC_KEYS => true, - METRIC_FILES => true, - METRIC_FILES_STORAGE => true, - METRIC_DEPLOYMENTS_STORAGE => true, - METRIC_BUILDS_STORAGE => true, - METRIC_DEPLOYMENTS => true, - METRIC_BUILDS => true, - METRIC_COLLECTIONS => true, - METRIC_DOCUMENTS => true, - METRIC_DATABASES_STORAGE => true, - ]; - - /** - * Skip metrics associated with parent IDs - * these need to be checked individually with `str_ends_with` - */ - protected array $skipParentIdMetrics = [ - '.files', - '.files.storage', - '.collections', - '.documents', - '.deployments', - '.deployments.storage', - '.builds', - '.builds.storage', - '.databases.storage' - ]; - - /** - * @var callable(Document): Database - */ - protected $getLogsDB; - - protected array $periods = [ - '1h' => 'Y-m-d H:00', - '1d' => 'Y-m-d 00:00', - 'inf' => '0000-00-00 00:00' - ]; - - public static function getName(): string - { - return 'stats-usage-dump'; - } - - /** - * @throws \Exception - */ - public function __construct() - { - $this - ->inject('message') - ->inject('getProjectDB') - ->inject('getLogsDB') - ->inject('register') - ->callback($this->action(...)); - } - - /** - * @param Message $message - * @param callable $getProjectDB - * @param callable $getLogsDB - * @param Registry $register - * @return void - * @throws Exception - * @throws \Throwable - * @throws \Utopia\Database\Exception - */ - public function action(Message $message, callable $getProjectDB, callable $getLogsDB, Registry $register): void - { - $this->getLogsDB = $getLogsDB; - $this->register = $register; - $payload = $message->getPayload() ?? []; - if (empty($payload)) { - throw new Exception('Missing payload'); - } - - foreach ($payload['stats'] ?? [] as $stats) { - $project = new Document($stats['project'] ?? []); - - $numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0; - $receivedAt = $stats['receivedAt'] ?? null; - if ($numberOfKeys === 0) { - continue; - } - - Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys); - - try { - /** @var Database $dbForProject */ - $dbForProject = $getProjectDB($project); - foreach ($stats['keys'] ?? [] as $key => $value) { - if ($value == 0) { - continue; - } - - if (str_contains($key, METRIC_DATABASES_STORAGE)) { - continue; - } - - foreach ($this->periods as $period => $format) { - $time = null; - - if ($period !== 'inf') { - $time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time()); - } - $id = \md5("{$time}_{$period}_{$key}"); - - $document = new Document([ - '$id' => $id, - 'period' => $period, - 'time' => $time, - 'metric' => $key, - 'value' => $value, - 'region' => System::getEnv('_APP_REGION', 'default'), - ]); - - $documentClone = clone $document; - - $dbForProject->createOrUpdateDocumentsWithIncrease( - 'stats', - 'value', - [$document] - ); - - $this->writeToLogsDB($project, $documentClone); - } - } - } catch (\Exception $e) { - Console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage()); - } - } - } - - protected function writeToLogsDB(Document $project, Document $document): void - { - if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') { - Console::log('Dual Writing is disabled. Skipping...'); - return; - } - - if (array_key_exists($document->getAttribute('metric'), $this->skipBaseMetrics)) { - return; - } - foreach ($this->skipParentIdMetrics as $skipMetric) { - if (str_ends_with($document->getAttribute('metric'), $skipMetric)) { - return; - } - } - - $dbForLogs = ($this->getLogsDB)($project); - - try { - $dbForLogs->createOrUpdateDocumentsWithIncrease( - 'stats', - 'value', - [$document] - ); - Console::success('Usage logs pushed to Logs DB'); - } catch (\Throwable $th) { - Console::error($th->getMessage()); - } - } -} diff --git a/src/Appwrite/SDK/Specification/Format.php b/src/Appwrite/SDK/Specification/Format.php index 8dbccf5cbb..af3b5c017e 100644 --- a/src/Appwrite/SDK/Specification/Format.php +++ b/src/Appwrite/SDK/Specification/Format.php @@ -113,6 +113,16 @@ abstract class Format protected function getEnumName(string $service, string $method, string $param): ?string { switch ($service) { + case 'proxy': + switch ($method) { + case 'createRedirectRule': + switch ($param) { + case 'resourceType': + return 'ProxyResourceType'; + } + break; + } + break; case 'console': switch ($method) { case 'getResource': @@ -441,7 +451,13 @@ abstract class Format case 'proxy': switch ($method) { case 'createRedirectRule': - return ['Moved Permanently 301', 'Found 302', 'Temporary Redirect 307', 'Permanent Redirect 308']; + switch ($param) { + case 'statusCode': + return ['Moved Permanently 301', 'Found 302', 'Temporary Redirect 307', 'Permanent Redirect 308']; + case 'resourceType': + return ['Site', 'Function']; + } + break; } break; case 'functions': diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 5b204c1910..15411f18ab 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -19,21 +19,14 @@ class Executor public const METHOD_CONNECT = 'CONNECT'; public const METHOD_TRACE = 'TRACE'; - private bool $selfSigned = false; - - /** - * @var callable(string, string): string $endpoint - */ - private $endpointSelector; + protected bool $selfSigned = false; + protected string $endpoint; protected array $headers; - /** - * @param callable(string, string): string $endpointSelector - */ - public function __construct(callable $endpointSelector) + public function __construct() { - $this->endpointSelector = $endpointSelector; + $this->endpoint = System::getEnv('_APP_EXECUTOR_HOST', ''); $this->headers = [ 'content-type' => 'application/json', 'authorization' => 'Bearer ' . System::getEnv('_APP_EXECUTOR_SECRET', ''), @@ -97,8 +90,8 @@ class Executor 'outputDirectory' => $outputDirectory ]; - $endpoint = $this->selectEndpoint($projectId, $deploymentId); - $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); + + $response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); $status = $response['headers']['status-code']; if ($status >= 400) { @@ -128,8 +121,7 @@ class Executor 'timeout' => $timeout ]; - $endpoint = $this->selectEndpoint($projectId, $deploymentId); - $this->call($endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback); + $this->call($this->endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback); } /** @@ -145,8 +137,7 @@ class Executor $runtimeId = "$projectId-$deploymentId" . $suffix; $route = "/runtimes/$runtimeId"; - $endpoint = $this->selectEndpoint($projectId, $deploymentId); - $response = $this->call($endpoint, self::METHOD_DELETE, $route, [ + $response = $this->call($this->endpoint, self::METHOD_DELETE, $route, [ 'x-opr-addressing-method' => 'broadcast' ], [], true, 30); @@ -239,8 +230,7 @@ class Executor $requestTimeout = $timeout + 15; } - $endpoint = $this->selectEndpoint($projectId, $deploymentId); - $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout); + $response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout); $status = $response['headers']['status-code']; if ($status >= 400) { @@ -274,8 +264,7 @@ class Executor 'timeout' => $timeout ]; - $endpoint = $this->selectEndpoint($projectId, $deploymentId); - $response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); + $response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout); $status = $response['headers']['status-code']; if ($status >= 400) { @@ -465,9 +454,4 @@ class Executor return $output; } - - private function selectEndpoint(string $projectId, string $deploymentId): string - { - return call_user_func($this->endpointSelector, $projectId, $deploymentId); - } } diff --git a/tests/e2e/Services/Proxy/ProxyBase.php b/tests/e2e/Services/Proxy/ProxyBase.php index 44e015b751..9f8e92d56f 100644 --- a/tests/e2e/Services/Proxy/ProxyBase.php +++ b/tests/e2e/Services/Proxy/ProxyBase.php @@ -68,7 +68,7 @@ trait ProxyBase return $rule; } - protected function createRedirectRule(string $domain, string $url, int $statusCode): mixed + protected function createRedirectRule(string $domain, string $url, int $statusCode, string $resourceType, string $resourceId): mixed { $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([ 'content-type' => 'application/json', @@ -77,6 +77,8 @@ trait ProxyBase 'domain' => $domain, 'url' => $url, 'statusCode' => $statusCode, + 'resourceType' => $resourceType, + 'resourceId' => $resourceId, ]); return $rule; @@ -115,9 +117,9 @@ trait ProxyBase return $rule['body']['$id']; } - protected function setupRedirectRule(string $domain, string $url, int $statusCode): string + protected function setupRedirectRule(string $domain, string $url, int $statusCode, string $resourceType, string $resourceId): string { - $rule = $this->createRedirectRule($domain, $url, $statusCode); + $rule = $this->createRedirectRule($domain, $url, $statusCode, $resourceType, $resourceId); $this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule)); diff --git a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php index 9a7ba74ec1..ea310d5449 100644 --- a/tests/e2e/Services/Proxy/ProxyCustomServerTest.php +++ b/tests/e2e/Services/Proxy/ProxyCustomServerTest.php @@ -131,7 +131,9 @@ class ProxyCustomServerTest extends Scope $response = $proxyClient->call(Client::METHOD_GET, '/todos/1'); $this->assertEquals(404, $response['headers']['status-code']); - $ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301); + $siteId = $this->setupSite()['siteId']; + + $ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301, 'site', $siteId); $this->assertNotEmpty($ruleId); $response = $proxyClient->call(Client::METHOD_GET, '/todos/1'); @@ -147,7 +149,7 @@ class ProxyCustomServerTest extends Scope $this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']); $domain = \uniqid() . '-redirect-307.custom.localhost'; - $ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 307); + $ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 307, 'site', $siteId); $this->assertNotEmpty($ruleId); $proxyClient = new Client(); @@ -158,6 +160,18 @@ class ProxyCustomServerTest extends Scope $this->assertEquals(307, $response['headers']['status-code']); $this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']); + $rules = $this->listRules([ + 'queries' => [ + Query::equal('type', ['redirect'])->toString(), + Query::equal('trigger', ['manual'])->toString(), + Query::equal('deploymentResourceType', ['site'])->toString(), + Query::equal('deploymentResourceId', [$siteId])->toString(), + ], + ]); + $this->assertEquals(200, $rules['headers']['status-code']); + $this->assertEquals(2, $rules['body']['total']); + + $this->cleanupSite($siteId); $this->cleanupRule($ruleId); } diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 8459e46c6f..b3ea045430 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -477,7 +477,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($domain); $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' ]); $this->assertNotEmpty($deploymentId); @@ -856,7 +856,7 @@ class SitesCustomServerTest extends Scope $deployment = $this->createDeployment($siteId, [ 'siteId' => $siteId, - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => true, ]); @@ -874,7 +874,7 @@ class SitesCustomServerTest extends Scope }, 50000, 500); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -917,7 +917,7 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -969,7 +969,7 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -1014,7 +1014,7 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -1022,7 +1022,7 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -1193,7 +1193,7 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -1318,7 +1318,7 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'false' ]); @@ -1818,7 +1818,7 @@ class SitesCustomServerTest extends Scope ]); $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => true ]); @@ -2435,7 +2435,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' ]); @@ -2477,7 +2477,7 @@ class SitesCustomServerTest extends Scope // test canceled deployment error page $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' ]); $deploymentId = $deployment['body']['$id'] ?? ''; @@ -2517,7 +2517,7 @@ class SitesCustomServerTest extends Scope $this->assertStringContainsString('View deployments', $response['body']); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('astro'), + 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' ]); @@ -2606,7 +2606,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => true ]); $this->assertEquals(202, $deployment['headers']['status-code']); @@ -2635,7 +2635,7 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), + 'code' => $this->packageSite('static-single-file'), 'activate' => true ]); $this->assertEquals(202, $deployment['headers']['status-code']);