diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index ab8e9195b3..01a0817b55 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -5,7 +5,6 @@ use Appwrite\Event\Build; use Appwrite\Event\Delete; use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Redirect; -use Appwrite\Platform\Modules\Compute\Base as ComputeBase; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -32,7 +31,10 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Cursor; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; use Utopia\Detector\Detection\Framework\Analog; use Utopia\Detector\Detection\Framework\Angular; use Utopia\Detector\Detection\Framework\Astro; @@ -434,8 +436,6 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } - ComputeBase::updateEmptyManualRule($project, $resource, $deployment, $dbForPlatform); - if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) { $retries = 0; $lockAcquired = false; @@ -1036,10 +1036,11 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') ->param('installationId', '', new Text(256), 'Installation Id') ->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('gitHub') ->inject('response') ->inject('dbForPlatform') - ->action(function (string $installationId, string $type, string $search, GitHub $github, Response $response, Database $dbForPlatform) { + ->action(function (string $installationId, string $type, string $search, array $queries, GitHub $github, Response $response, Database $dbForPlatform) { if (empty($search)) { $search = ""; } @@ -1055,11 +1056,20 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); - $page = 1; - $perPage = 4; + $queries = Query::parseQueries($queries); + $limitQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_LIMIT)); + $offsetQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_OFFSET)); + $limit = !empty($limitQuery) ? $limitQuery->getValue() : 4; + $offset = !empty($offsetQuery) ? $offsetQuery->getValue() : 0; + + if ($offset % $limit !== 0) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'offset must be a multiple of the limit'); + } + + $page = ($offset / $limit) + 1; $owner = $github->getOwnerName($providerInstallationId); - $repos = $github->searchRepositories($owner, $page, $perPage, $search); + ['items' => $repos, 'total' => $total] = $github->searchRepositories($owner, $page, $limit, $search); $repos = \array_map(function ($repo) use ($installation) { $repo['id'] = \strval($repo['id'] ?? ''); @@ -1231,7 +1241,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') $response->dynamic(new Document([ $type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos, - 'total' => \count($repos), + 'total' => $total, ]), ($type === 'framework') ? Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST); }); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 05477d679a..16d44481b6 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -856,7 +856,7 @@ App::shutdown() * * Therefore, we consider this an anonymous request and create a relevant user. */ - $user = new Document([ + $user = new User([ '$id' => '', 'status' => true, 'type' => ACTIVITY_TYPE_GUEST, diff --git a/app/init/constants.php b/app/init/constants.php index 0cec24b749..ea5c0fb2c5 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -93,6 +93,13 @@ const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io'; const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite'; const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled'; +/** + * JWT for Resource Tokens. + */ +const RESOURCE_TOKEN_ALGORITHM = 'HS256'; +const RESOURCE_TOKEN_MAX_AGE = 86400 * 365 * 10; /* 10 years */ +const RESOURCE_TOKEN_LEEWAY = 10; // 10 seconds + /** * Token Expiration times. */ diff --git a/app/init/resources.php b/app/init/resources.php index 0c05b30a83..98162d3a2b 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -1003,7 +1003,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) { $tokenJWT = $request->getParam('token'); if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. + // Use a large but reasonable maxAge to avoid auto-exp when token has no expiry + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // Instantiate with key, algo, maxAge and leeway. try { $payload = $jwt->decode($tokenJWT); diff --git a/composer.json b/composer.json index 0eec77cb95..25d3c06c8d 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "utopia-php/swoole": "0.8.*", "utopia-php/system": "0.9.*", "utopia-php/telemetry": "0.1.*", - "utopia-php/vcs": "0.12.*", + "utopia-php/vcs": "0.13.*", "utopia-php/websocket": "0.3.*", "matomo/device-detector": "6.4.*", "dragonmantank/cron-expression": "3.4.*", diff --git a/composer.lock b/composer.lock index 65dd0dd236..4dccb29a1a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "479b161d08b29d22267c4c7798751842", + "content-hash": "0cad126c9b41c0d496462ba03ff36d7b", "packages": [ { "name": "adhocore/jwt", @@ -891,16 +891,16 @@ }, { "name": "matomo/device-detector", - "version": "6.4.7", + "version": "6.4.8", "source": { "type": "git", "url": "https://github.com/matomo-org/device-detector.git", - "reference": "e53eed31bb1530851feebe52bd64c3451da19e77" + "reference": "56baf981af4f192e15a4f369d4975af847a81ccb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/e53eed31bb1530851feebe52bd64c3451da19e77", - "reference": "e53eed31bb1530851feebe52bd64c3451da19e77", + "url": "https://api.github.com/repos/matomo-org/device-detector/zipball/56baf981af4f192e15a4f369d4975af847a81ccb", + "reference": "56baf981af4f192e15a4f369d4975af847a81ccb", "shasum": "" }, "require": { @@ -957,7 +957,7 @@ "source": "https://github.com/matomo-org/matomo", "wiki": "https://dev.matomo.org/" }, - "time": "2025-08-20T17:20:16+00:00" + "time": "2025-11-26T16:02:47+00:00" }, { "name": "mongodb/mongodb", @@ -2673,16 +2673,16 @@ }, { "name": "symfony/http-client", - "version": "v7.3.6", + "version": "v7.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de" + "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", - "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", + "url": "https://api.github.com/repos/symfony/http-client/zipball/ee5e0e0139ab506f6063a230e631bed677c650a4", + "reference": "ee5e0e0139ab506f6063a230e631bed677c650a4", "shasum": "" }, "require": { @@ -2713,12 +2713,13 @@ "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -2749,7 +2750,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.6" + "source": "https://github.com/symfony/http-client/tree/v7.4.0" }, "funding": [ { @@ -2769,7 +2770,7 @@ "type": "tidelift" } ], - "time": "2025-11-05T17:41:46+00:00" + "time": "2025-11-20T12:32:50+00:00" }, { "name": "symfony/http-client-contracts", @@ -5211,16 +5212,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.12.0", + "version": "0.13.0", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "28457cf347972c4ec95d3ca77776a4921364a665" + "reference": "c59e21db5ca42014fe2071fec3c2f814efcc86dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/28457cf347972c4ec95d3ca77776a4921364a665", - "reference": "28457cf347972c4ec95d3ca77776a4921364a665", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/c59e21db5ca42014fe2071fec3c2f814efcc86dd", + "reference": "c59e21db5ca42014fe2071fec3c2f814efcc86dd", "shasum": "" }, "require": { @@ -5254,9 +5255,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.12.0" + "source": "https://github.com/utopia-php/vcs/tree/0.13.0" }, - "time": "2025-10-22T12:58:29+00:00" + "time": "2025-11-28T08:42:31+00:00" }, { "name": "utopia-php/websocket", @@ -7929,47 +7930,39 @@ }, { "name": "symfony/console", - "version": "v7.3.6", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" + "reference": "307d3cf852f5ead3618ac60ecbedbdd512c348b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", - "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "url": "https://api.github.com/repos/symfony/console/zipball/307d3cf852f5ead3618ac60ecbedbdd512c348b1", + "reference": "307d3cf852f5ead3618ac60ecbedbdd512c348b1", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.2" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/string": "^7.4|^8.0" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8003,7 +7996,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.6" + "source": "https://github.com/symfony/console/tree/v8.0.0" }, "funding": [ { @@ -8023,29 +8016,29 @@ "type": "tidelift" } ], - "time": "2025-11-04T01:21:42+00:00" + "time": "2025-11-21T13:19:49+00:00" }, { "name": "symfony/filesystem", - "version": "v7.3.6", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" + "reference": "7fc96ae83372620eaba3826874f46e26295768ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", - "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7fc96ae83372620eaba3826874f46e26295768ca", + "reference": "7fc96ae83372620eaba3826874f46e26295768ca", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^6.4|^7.0" + "symfony/process": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8073,7 +8066,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.6" + "source": "https://github.com/symfony/filesystem/tree/v8.0.0" }, "funding": [ { @@ -8093,27 +8086,27 @@ "type": "tidelift" } ], - "time": "2025-11-05T09:52:27+00:00" + "time": "2025-11-05T14:36:47+00:00" }, { "name": "symfony/finder", - "version": "v7.3.5", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f" + "reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", - "reference": "9f696d2f1e340484b4683f7853b273abff94421f", + "url": "https://api.github.com/repos/symfony/finder/zipball/7598dd5770580fa3517ec83e8da0c9b9e01f4291", + "reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8141,7 +8134,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.5" + "source": "https://github.com/symfony/finder/tree/v8.0.0" }, "funding": [ { @@ -8161,24 +8154,24 @@ "type": "tidelift" } ], - "time": "2025-10-15T18:45:57+00:00" + "time": "2025-11-05T14:36:47+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.3", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d2b592535ffa6600c265a3893a7f7fd2bad82dd7", + "reference": "d2b592535ffa6600c265a3893a7f7fd2bad82dd7", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -8212,7 +8205,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" + "source": "https://github.com/symfony/options-resolver/tree/v8.0.0" }, "funding": [ { @@ -8232,7 +8225,7 @@ "type": "tidelift" } ], - "time": "2025-08-05T10:16:07+00:00" + "time": "2025-11-12T15:55:31+00:00" }, { "name": "symfony/polyfill-ctype", @@ -8566,20 +8559,20 @@ }, { "name": "symfony/process", - "version": "v7.3.4", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" + "reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", - "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", + "url": "https://api.github.com/repos/symfony/process/zipball/a0a750500c4ce900d69ba4e9faf16f82c10ee149", + "reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4" }, "type": "library", "autoload": { @@ -8607,7 +8600,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.4" + "source": "https://github.com/symfony/process/tree/v8.0.0" }, "funding": [ { @@ -8627,38 +8620,38 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-10-16T16:25:44+00:00" }, { "name": "symfony/string", - "version": "v7.3.4", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f96476035142921000338bad71e5247fbc138872" + "reference": "f929eccf09531078c243df72398560e32fa4cf4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", - "reference": "f96476035142921000338bad71e5247fbc138872", + "url": "https://api.github.com/repos/symfony/string/zipball/f929eccf09531078c243df72398560e32fa4cf4f", + "reference": "f929eccf09531078c243df72398560e32fa4cf4f", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8697,7 +8690,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.4" + "source": "https://github.com/symfony/string/tree/v8.0.0" }, "funding": [ { @@ -8717,7 +8710,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T14:36:48+00:00" + "time": "2025-09-11T14:37:55+00:00" }, { "name": "textalk/websocket", diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php index 08d5e05454..10230df7af 100644 --- a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php @@ -144,7 +144,8 @@ class Create extends Action $challenge = $dbForProject->createDocument('challenges', $challenge); - $templatesPath = \dirname(__DIR__, 7) . '/app/config/locale/templates'; + // 9 levels up to project root + $templatesPath = \dirname(__DIR__, 9) . '/app/config/locale/templates'; switch ($factor) { case Type::PHONE: diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index cc9b403925..a538eb1497 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -13,7 +13,6 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Swoole\Request; use Utopia\System\System; @@ -53,7 +52,7 @@ class Base extends Action return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT; } - public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document + public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document { $deploymentId = ID::unique(); $entrypoint = $function->getAttribute('entrypoint', ''); @@ -134,8 +133,6 @@ class Base extends Action ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); $dbForProject->updateDocument('functions', $function->getId(), $function); - $this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform); - $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($function) @@ -330,8 +327,6 @@ class Base extends Action } } - $this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform); - $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($site) @@ -340,34 +335,4 @@ class Base extends Action return $deployment; } - - /** - * Update empty manual rule for deployment. - * In case of first deployment, deployment ID will be empty in the rules, so we need to update it here. - * - * @param \Utopia\Database\Document $project - * @param \Utopia\Database\Document $resource - * @param \Utopia\Database\Document $deployment - * @param \Utopia\Database\Database $dbForPlatform - * @return void - */ - public static function updateEmptyManualRule(Document $project, Document $resource, Document $deployment, Database $dbForPlatform) - { - $resourceType = $resource->getCollection() === 'sites' ? 'site' : 'function'; - - $queries = [ - Query::equal('projectInternalId', [$project->getSequence()]), - Query::equal('deploymentResourceInternalId', [$resource->getSequence()]), - Query::equal('deploymentResourceType', [$resourceType]), - Query::equal('deploymentId', ['']), - Query::equal('type', ['deployment']), - Query::equal('trigger', ['manual']), - ]; - $dbForPlatform->forEach('rules', function (Document $rule) use ($deployment, $dbForPlatform) { - Authorization::skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([ - 'deploymentId' => $deployment->getId(), - 'deploymentInternalId' => $deployment->getSequence(), - ]))); - }, $queries); - } } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php index d56b0d4d8f..d53324b27f 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -32,7 +31,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\Nullable; use Utopia\Validator\Text; -class Create extends Base +class Create extends Action { use HTTP; @@ -83,7 +82,6 @@ class Create extends Base ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('dbForPlatform') ->inject('queueForEvents') ->inject('project') ->inject('deviceForFunctions') @@ -102,7 +100,6 @@ class Create extends Base Request $request, Response $response, Database $dbForProject, - Database $dbForPlatform, Event $queueForEvents, Document $project, Device $deviceForFunctions, @@ -304,8 +301,6 @@ class Create extends Base } } - $this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform); - $metadata = null; $queueForEvents diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php index 19e1653dcb..00c29d6bba 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php @@ -124,7 +124,6 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, - dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, @@ -171,9 +170,6 @@ class Create extends Base ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); $dbForProject->updateDocument('functions', $function->getId(), $function); - - $this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform); - $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($function) diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php index 79e2e215ad..0ad9852722 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php @@ -105,7 +105,6 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, - dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index b22710253e..ec2a4baac5 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -305,14 +305,12 @@ class Create extends Base $template = new Document(); $installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId')); - // TODO: Is this still needed? Can this be removed? $deployment = $this->redeployVcsFunction( request: $request, function: $function, project: $project, installation: $installation, dbForProject: $dbForProject, - dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php index 4bc3dca613..318c2a2032 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php @@ -273,7 +273,7 @@ class Update extends Base // Redeploy logic if (!$isConnected && !empty($providerRepositoryId)) { - $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $dbForPlatform, $queueForBuilds, new Document(), $github, true); + $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true); } // Inform scheduler if function is still active diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php index 0662f09b80..aa622d8d84 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -33,7 +32,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\Nullable; use Utopia\Validator\Text; -class Create extends Base +class Create extends Action { use HTTP; @@ -366,8 +365,6 @@ class Create extends Base } } - $this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform); - $metadata = null; $queueForEvents diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php index f5d7d5a873..dc7d4c4ace 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php @@ -208,8 +208,6 @@ class Create extends Base ])) ); - $this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform); - $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($site) diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 61d59d4da0..72ec04a2a5 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -272,7 +272,7 @@ class Update extends Base // Redeploy logic if (!$isConnected && !empty($providerRepositoryId)) { - $this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $dbForPlatform, $queueForBuilds, new Document(), $github, true); + $this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true); } $queueForEvents->setParam('siteId', $site->getId()); diff --git a/src/Appwrite/Utopia/Response/Model/ResourceToken.php b/src/Appwrite/Utopia/Response/Model/ResourceToken.php index 87ab66ab5d..c2b3deb56b 100644 --- a/src/Appwrite/Utopia/Response/Model/ResourceToken.php +++ b/src/Appwrite/Utopia/Response/Model/ResourceToken.php @@ -64,7 +64,7 @@ class ResourceToken extends Model $expire = $document->getAttribute('expire'); // Use a large but reasonable maxAge to avoid auto-exp when we set explicit exp - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // 10 years $payload = [ 'tokenId' => $document->getId(), @@ -73,13 +73,13 @@ class ResourceToken extends Model 'resourceInternalId' => $document->getAttribute('resourceInternalId'), ]; + $createdDate = new \DateTime($document->getCreatedAt()); + $payload['iat'] = $createdDate->getTimestamp(); + // Set explicit expiration in JWT payload if we have an expiry date if ($expire !== null) { $expiryDate = new \DateTime($expire); $payload['exp'] = $expiryDate->getTimestamp(); - } else { - // For infinite expiry, set 'iat' to prevent JWT library from auto-adding 'exp' - $payload['iat'] = time(); } $secret = $jwt->encode($payload); diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 492226b01a..7403b23a73 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -432,27 +432,4 @@ trait FunctionsBase return $specifications; } - - protected function createFunctionRule(string $functionId, string $domain): mixed - { - $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'functionId' => $functionId, - 'domain' => $domain, - ]); - - return $rule; - } - - protected function getFunctionRule(string $ruleId): mixed - { - $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - return $rule; - } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 74b6ed3e8d..8cc986b072 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -14,7 +14,6 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\System\System; class FunctionsCustomServerTest extends Scope { @@ -393,10 +392,6 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $rule = $this->createFunctionRule($functionId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createTemplateDeployment( $functionId, [ @@ -413,20 +408,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $rule = $this->getFunctionRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString("The page will update after the build completes.", $response['body']); - - $deployment = $this->getDeployment($functionId, $deployment['body']['$id']); - $this->assertEquals(200, $deployment['headers']['status-code']); // Wait for deployment to be ready $deploymentId = $deployment['body']['$id']; $this->assertEventually(function () use ($functionId, $deploymentId) { @@ -436,6 +417,7 @@ class FunctionsCustomServerTest extends Scope // Verify deployment sizes $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertGreaterThan(0, $deployment['body']['sourceSize']); $this->assertGreaterThan(0, $deployment['body']['buildSize']); $totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize']; @@ -680,10 +662,6 @@ class FunctionsCustomServerTest extends Scope */ $functionId = $data['functionId']; - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $rule = $this->createFunctionRule($functionId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -695,18 +673,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.js', $deployment['body']['entrypoint']); - $rule = $this->getFunctionRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); - $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($functionId, $deploymentIdActive) { @@ -2426,12 +2392,6 @@ class FunctionsCustomServerTest extends Scope $domain = $this->setupFunctionDomain($functionId); $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); - $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -2444,22 +2404,11 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ])); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString('Deployment is still building', $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); // canceled deployment - $functionId = $this->setupFunction([ - 'functionId' => ID::unique(), - 'name' => 'Test Error Pages', - 'runtime' => 'node-22', - 'entrypoint' => 'index.js', - 'timeout' => 15, - 'commands' => 'cd non-existing-directory', - 'execute' => ['any'] - ]); - $domain = $this->setupFunctionDomain($functionId); - $proxyClient->setEndpoint('http://' . $domain); $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -2477,9 +2426,9 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ])); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString('Deployment build canceled', $response['body']); - $this->assertStringContainsString('This build was canceled and won\'t be deployed.', $response['body']); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); $this->cleanupFunction($functionId); } diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 6abe1b38d9..7eb5d9699c 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -474,27 +474,4 @@ trait SitesBase return $specifications; } - - protected function createSiteRule(string $siteId, string $domain): mixed - { - $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'siteId' => $siteId, - 'domain' => $domain, - ]); - - return $rule; - } - - protected function getSiteRule(string $ruleId): mixed - { - $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - return $rule; - } } diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 413fea289f..b7dc9e7334 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -861,10 +861,6 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_SITES', ''); - $rule = $this->createSiteRule($siteId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createDeployment($siteId, [ 'siteId' => $siteId, 'code' => $this->packageSite('static-single-file'), @@ -876,18 +872,6 @@ class SitesCustomServerTest extends Scope $this->assertEquals('waiting', $deployment['body']['status']); $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); - $rule = $this->getSiteRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); - $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($siteId, $deploymentIdActive) { @@ -1579,10 +1563,6 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_SITES', ''); - $rule = $this->createSiteRule($siteId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createTemplateDeployment($siteId, [ 'repository' => $template['providerRepositoryId'], 'owner' => $template['providerOwner'], @@ -1595,18 +1575,6 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $rule = $this->getSiteRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); - $deployment = $this->getDeployment($siteId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); @@ -1618,6 +1586,10 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); + $domain = $this->setupSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(200, $response['headers']['status-code']); @@ -2612,8 +2584,7 @@ class SitesCustomServerTest extends Scope }, 100000, 500); $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString('Deployment build failed', $response['body']); + $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']); $this->cleanupSite($siteId); } @@ -2708,12 +2679,6 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $domain = $this->setupSiteDomain($siteId); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); // test canceled deployment error page $deployment = $this->createDeployment($siteId, [ @@ -2749,6 +2714,13 @@ class SitesCustomServerTest extends Scope $this->assertStringContainsString("Deployment build canceled", $response['body']); $this->assertStringContainsString("View deployments", $response['body']); + // check site domain for no active deployments + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); + $deployment = $this->createDeployment($siteId, [ 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' diff --git a/tests/e2e/Services/Tokens/TokensConsoleClientTest.php b/tests/e2e/Services/Tokens/TokensConsoleClientTest.php index f1480faba0..7a9181b1dc 100644 --- a/tests/e2e/Services/Tokens/TokensConsoleClientTest.php +++ b/tests/e2e/Services/Tokens/TokensConsoleClientTest.php @@ -72,37 +72,45 @@ class TokensConsoleClientTest extends Scope $this->assertEquals(400, $token['headers']['status-code']); $this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']); - // Success case: No expire date - $token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'expire' => null, - ]); + // Success cases: With & without expiry + $expireList = [null, date('Y-m-d', strtotime("tomorrow"))]; + foreach ($expireList as $expire) { + $token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ + 'expire' => $expire, + ]); - $this->assertEquals(201, $token['headers']['status-code']); - $this->assertEquals('files', $token['body']['resourceType']); - $this->assertNotEmpty($token['body']['$id']); - $this->assertNotEmpty($token['body']['secret']); + $this->assertEquals(201, $token['headers']['status-code']); + $this->assertEquals('files', $token['body']['resourceType']); + $this->assertNotEmpty($token['body']['$id']); + $this->assertNotEmpty($token['body']['secret']); - // Verify the generated token JWT contains correct resource information - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years maxAge - try { - $payload = $jwt->decode($token['body']['secret']); - $this->assertIsArray($payload, 'JWT payload should decode to an array'); - $this->assertArrayHasKey('tokenId', $payload, 'JWT payload should contain tokenId'); - $this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId'); - $this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType'); - $this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId'); + // Verify the generated token JWT contains correct resource information + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years maxAge + try { + $payload = $jwt->decode($token['body']['secret']); + $this->assertIsArray($payload, 'JWT payload should decode to an array'); + $this->assertArrayHasKey('tokenId', $payload, 'JWT payload should contain tokenId'); + $this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId'); + $this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType'); + $this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId'); + $this->assertArrayHasKey('iat', $payload, 'JWT payload should contain iat'); - $this->assertEquals($token['body']['$id'], $payload['tokenId'], 'JWT tokenId should match token ID'); - $this->assertEquals($bucketId . ':' . $fileId, $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format'); - $this->assertEquals('files', $payload['resourceType'], 'JWT resourceType should be files'); + if (!empty($expire)) { + $this->assertArrayHasKey('exp', $payload, 'JWT payload should contain exp'); + } else { + $this->assertArrayNotHasKey('exp', $payload, 'JWT payload should not contain exp field for tokens without expiry'); + } - // For newly created tokens without expiry, should not have exp field - $this->assertArrayNotHasKey('exp', $payload, 'JWT payload should not contain exp field for tokens without expiry'); - } catch (JWTException $e) { - $this->fail('Failed to decode JWT: ' . $e->getMessage()); + $this->assertEquals($token['body']['$id'], $payload['tokenId'], 'JWT tokenId should match token ID'); + $this->assertEquals($bucketId . ':' . $fileId, $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format'); + $this->assertEquals('files', $payload['resourceType'], 'JWT resourceType should be files'); + + } catch (JWTException $e) { + $this->fail('Failed to decode JWT: ' . $e->getMessage()); + } } return [ @@ -218,6 +226,11 @@ class TokensConsoleClientTest extends Scope $this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId'); $this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType'); $this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId'); + $this->assertArrayHasKey('iat', $payload, 'JWT payload should contain iat'); + + if (!empty($token['expire'])) { + $this->assertArrayHasKey('exp', $payload, 'JWT payload should contain exp'); + } $this->assertEquals($token['$id'], $payload['tokenId'], 'JWT tokenId should match token ID'); $this->assertEquals($data['bucketId'] . ':' . $data['fileId'], $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format'); diff --git a/tests/e2e/Services/VCS/VCSConsoleClientTest.php b/tests/e2e/Services/VCS/VCSConsoleClientTest.php index 13c3ddb251..963aa5a84b 100644 --- a/tests/e2e/Services/VCS/VCSConsoleClientTest.php +++ b/tests/e2e/Services/VCS/VCSConsoleClientTest.php @@ -10,6 +10,7 @@ use Utopia\Cache\Adapter\None; use Utopia\Cache\Cache; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; use Utopia\System\System; use Utopia\VCS\Adapter\Git\GitHub; @@ -316,6 +317,43 @@ class VCSConsoleClientTest extends Scope $this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['name'], 'appwrite'); $this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['runtime'], 'other'); + // with limit and offset + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(1)->toString(), + 'offset' => Query::offset(0)->toString() + ]); + $this->assertSame(200, $repositories['headers']['status-code']); + $this->assertSame(4, $repositories['body']['total']); + $this->assertCount(1, $repositories['body']['runtimeProviderRepositories']); + $this->assertSame('starter-for-svelte', $repositories['body']['runtimeProviderRepositories'][0]['name']); + + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(2)->toString(), + 'offset' => Query::offset(2)->toString() + ]); + $this->assertSame(200, $repositories['headers']['status-code']); + $this->assertSame(4, $repositories['body']['total']); + $this->assertCount(2, $repositories['body']['runtimeProviderRepositories']); + $this->assertSame('appwrite', $repositories['body']['runtimeProviderRepositories'][0]['name']); + $this->assertSame('ruby-starter', $repositories['body']['runtimeProviderRepositories'][1]['name']); + + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(2)->toString(), + 'offset' => Query::offset(100)->toString() + ]); + $this->assertSame(200, $repositories['headers']['status-code']); + $this->assertSame(4, $repositories['body']['total']); + $this->assertCount(0, $repositories['body']['runtimeProviderRepositories']); + // TODO: If you are about to add another check, rewrite this to @provideScenarios /** @@ -338,6 +376,17 @@ class VCSConsoleClientTest extends Scope $this->assertEquals(400, $repositories['headers']['status-code']); + // invalid offset + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(2)->toString(), + 'offset' => Query::offset(1)->toString() + ]); + $this->assertEquals(400, $repositories['headers']['status-code']); + $this->assertEquals('offset must be a multiple of the limit', $repositories['body']['message']); + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [