diff --git a/app/config/collections.php b/app/config/collections.php index 1d452f4b3a..db229ce87a 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1944,6 +1944,17 @@ $projectCollections = array_merge([ 'array' => false, 'filters' => ['subQueryVariables'], ], + [ + '$id' => ID::custom('varsProject'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['subQueryProjectVariables'], + ], [ '$id' => ID::custom('events'), 'type' => Database::VAR_STRING, diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 4da67328cd..7bc408b16a 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -927,10 +927,9 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->param('deploymentId', '', new UID(), 'Deployment ID.') ->inject('response') ->inject('dbForProject') - ->inject('project') ->inject('events') ->inject('dbForConsole') - ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $events, Database $dbForConsole) { + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $events, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -990,9 +989,8 @@ App::delete('/v1/functions/:functionId') ->inject('dbForProject') ->inject('deletes') ->inject('events') - ->inject('project') ->inject('dbForConsole') - ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $deletes, Event $events, Document $project, Database $dbForConsole) { + ->action(function (string $functionId, Response $response, Database $dbForProject, Delete $deletes, Event $events, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); @@ -1434,11 +1432,9 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('dbForConsole') ->inject('project') - ->inject('gitHub') ->inject('events') - ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, GitHub $github, Event $events) use ($redeployVcs) { + ->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $events) use ($redeployVcs) { $function = $dbForProject->getDocument('functions', $functionId); @@ -1662,15 +1658,14 @@ App::post('/v1/functions/:functionId/executions') $vars = []; // Shared vars - foreach ($project->getAttribute('variables', []) as $var) { + foreach ($function->getAttribute('varsProject', []) as $var) { $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); } // Function vars - $vars = \array_merge($vars, array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; - return $carry; - }, [])); + foreach ($function->getAttribute('vars', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } // Appwrite vars $vars = \array_merge($vars, [ @@ -1905,11 +1900,10 @@ App::post('/v1/functions/:functionId/variables') ->param('functionId', '', new UID(), 'Function unique ID.', false) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) - ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->action(function (string $functionId, string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) { + ->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -1938,7 +1932,6 @@ App::post('/v1/functions/:functionId/variables') } catch (DuplicateException $th) { throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); } - $dbForConsole->deleteCachedDocument('projects', $project->getId()); $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); @@ -1950,8 +1943,6 @@ App::post('/v1/functions/:functionId/variables') ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); - $dbForProject->deleteCachedDocument('functions', $function->getId()); - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($variable, Response::MODEL_VARIABLE); @@ -1979,8 +1970,8 @@ App::get('/v1/functions/:functionId/variables') } $response->dynamic(new Document([ - 'variables' => $function->getAttribute('vars'), - 'total' => \count($function->getAttribute('vars')), + 'variables' => $function->getAttribute('vars', []), + 'total' => \count($function->getAttribute('vars', [])), ]), Response::MODEL_VARIABLE_LIST); }); @@ -2040,11 +2031,10 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true) - ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->action(function (string $functionId, string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) { + ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); @@ -2071,7 +2061,6 @@ App::put('/v1/functions/:functionId/variables/:variableId') } catch (DuplicateException $th) { throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); } - $dbForConsole->deleteCachedDocument('projects', $project->getId()); $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); @@ -2083,8 +2072,6 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); - $dbForProject->deleteCachedDocument('functions', $function->getId()); - $response->dynamic($variable, Response::MODEL_VARIABLE); }); @@ -2102,11 +2089,10 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('functionId', '', new UID(), 'Function unique ID.', false) ->param('variableId', '', new UID(), 'Variable unique ID.', false) - ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('dbForConsole') - ->action(function (string $functionId, string $variableId, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) { + ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) { $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { @@ -2134,7 +2120,5 @@ App::delete('/v1/functions/:functionId/variables/:variableId') ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); - $dbForProject->deleteCachedDocument('functions', $function->getId()); - $response->noContent(); }); diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index 0f0a296185..bd8d9d5f73 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -161,7 +161,6 @@ App::post('/v1/project/variables') } catch (DuplicateException $th) { throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); } - $dbForConsole->deleteCachedDocument('projects', $project->getId()); $functions = $dbForProject->find('functions', [ Query::limit(APP_LIMIT_SUBQUERY) @@ -171,8 +170,6 @@ App::post('/v1/project/variables') $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); } - $dbForProject->deleteCachedDocument('projects', $project->getId()); - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($variable, Response::MODEL_VARIABLE); @@ -261,7 +258,6 @@ App::put('/v1/project/variables/:variableId') } catch (DuplicateException $th) { throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); } - $dbForConsole->deleteCachedDocument('projects', $project->getId()); $functions = $dbForProject->find('functions', [ Query::limit(APP_LIMIT_SUBQUERY) @@ -271,8 +267,6 @@ App::put('/v1/project/variables/:variableId') $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); } - $dbForProject->deleteCachedDocument('projects', $project->getId()); - $response->dynamic($variable, Response::MODEL_VARIABLE); }); @@ -296,6 +290,8 @@ App::delete('/v1/project/variables/:variableId') throw new Exception(Exception::VARIABLE_NOT_FOUND); } + $dbForProject->deleteDocument('variables', $variable->getId()); + $functions = $dbForProject->find('functions', [ Query::limit(APP_LIMIT_SUBQUERY) ]); @@ -304,8 +300,5 @@ App::delete('/v1/project/variables/:variableId') $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); } - $dbForProject->deleteDocument('variables', $variable->getId()); - $dbForProject->deleteCachedDocument('projects', $project->getId()); - $response->noContent(); }); diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 543a7e6b8b..fc352e4809 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -228,6 +228,7 @@ App::get('/v1/vcs/github/authorize') ->groups(['api', 'vcs']) ->label('scope', 'vcs.read') ->label('sdk.namespace', 'vcs') + ->label('error', __DIR__ . '/../../views/general/error.phtml') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.method', 'createGitHubInstallation') ->label('sdk.description', '') @@ -248,6 +249,11 @@ App::get('/v1/vcs/github/authorize') ]); $appName = App::getEnv('_APP_VCS_GITHUB_APP_NAME'); + + if (empty($appName)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.'); + } + $url = "https://github.com/apps/$appName/installations/new?" . \http_build_query([ 'state' => $state, 'redirect_uri' => $request->getProtocol() . '://' . $request->getHostname() . "/v1/vcs/github/callback" diff --git a/app/init.php b/app/init.php index 91ded9f017..6f195742ee 100644 --- a/app/init.php +++ b/app/init.php @@ -493,7 +493,6 @@ Database::addFilter( } ); -// READ-ONLY! TO update, write directly to 'variables' collection. After update to vars, make sure to deleteCachedDocument() Database::addFilter( 'subQueryProjectVariables', function (mixed $value) { diff --git a/app/workers/builds.php b/app/workers/builds.php index 2088db7b8b..5f8ea9c4ce 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -321,21 +321,15 @@ class BuildsV1 extends Worker $vars = []; - // Global vars - $varsFromProject = $dbForProject->find('variables', [ - Query::equal('resourceType', ['project']), - Query::limit(APP_LIMIT_SUBQUERY) - ]); - - foreach ($varsFromProject as $var) { - $vars[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; + // Shared vars + foreach ($function->getAttribute('varsProject', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); } // Function vars - $vars = \array_merge($vars, array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value'); - return $carry; - }, [])); + foreach ($function->getAttribute('vars', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } // Appwrite vars $vars = \array_merge($vars, [ diff --git a/app/workers/functions.php b/app/workers/functions.php index d35c8c791a..aaad7d237e 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -143,17 +143,14 @@ Server::setResource('execute', function () { $vars = []; // Shared vars - $varsShared = $project->getAttribute('variables', []); - $vars = \array_merge($vars, \array_reduce($varsShared, function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? ''; - return $carry; - }, [])); + foreach ($function->getAttribute('varsProject', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } // Function vars - $vars = \array_merge($vars, array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) { - $carry[$var->getAttribute('key')] = $var->getAttribute('value'); - return $carry; - }, [])); + foreach ($function->getAttribute('vars', []) as $var) { + $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); + } // Appwrite vars $vars = \array_merge($vars, [ diff --git a/src/Appwrite/Auth/OAuth2/Exception.php b/src/Appwrite/Auth/OAuth2/Exception.php index 28d8d652f9..df5054ae9a 100644 --- a/src/Appwrite/Auth/OAuth2/Exception.php +++ b/src/Appwrite/Auth/OAuth2/Exception.php @@ -17,14 +17,17 @@ class Exception extends AppwriteException $decoded = json_decode($response, true); if (\is_array($decoded)) { if (\is_array($decoded['error'] ?? '')) { - $this->error = $decoded['error']['status']; - $this->errorDescription = $decoded['error']['message']; - $this->message = $this->error . ': ' . $this->errorDescription; + $this->error = $decoded['error']['status'] ?? 'Unknown error'; + $this->errorDescription = $decoded['error']['message'] ?? 'No description'; + } elseif (\is_array($decoded['errors'] ?? '')) { + $this->error = $decoded['error'] ?? $decoded['message'] ?? 'Unknown error'; + $this->errorDescription = $decoded['errors'][0]['message'] ?? 'No description'; } else { $this->error = $decoded['error'] ?? $decoded['message'] ?? 'Unknown error'; $this->errorDescription = $decoded['error_description'] ?? 'No description'; - $this->message = $this->error . ': ' . $this->errorDescription; } + + $this->message = $this->error . ': ' . $this->errorDescription; } $type = match ($code) { 400 => AppwriteException::USER_OAUTH2_BAD_REQUEST, diff --git a/src/Appwrite/Migration/Version/V19.php b/src/Appwrite/Migration/Version/V19.php index 33c0fceb9f..601f122ad9 100644 --- a/src/Appwrite/Migration/Version/V19.php +++ b/src/Appwrite/Migration/Version/V19.php @@ -408,6 +408,7 @@ class V19 extends Migration 'version', 'entrypoint', 'commands', + 'varsProject' ]; foreach ($attributesToCreate as $attribute) { try { diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 5350d9d0e5..60b7f7542e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -343,6 +343,24 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']); $this->assertEquals(15, $response1['body']['timeout']); + /** + * Create global variable to test in execution later + */ + $headers = [ + 'content-type' => 'application/json', + 'origin' => 'http://localhost', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-mode' => 'admin', + ]; + + $variable = $this->client->call(Client::METHOD_POST, '/project/variables', $headers, [ + 'key' => 'GLOBAL_VARIABLE', + 'value' => 'Global Variable Value', + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + /** * Test for FAILURE */ @@ -640,6 +658,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('http', $execution['body']['responseBody']); $this->assertStringContainsString('PHP', $execution['body']['responseBody']); $this->assertStringContainsString('8.0', $execution['body']['responseBody']); + $this->assertStringContainsString('Global Variable Value', $execution['body']['responseBody']); // $this->assertStringContainsString('êä', $execution['body']['responseBody']); // tests unknown utf-8 chars $this->assertEquals('', $execution['body']['errors']); $this->assertEquals('', $execution['body']['logs']); diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php index 5a9666488e..ac7b85a43a 100644 --- a/tests/resources/functions/php/index.php +++ b/tests/resources/functions/php/index.php @@ -8,6 +8,7 @@ return function ($context) { 'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '', - 'UNICODE_TEST' => "êä" + 'UNICODE_TEST' => "êä", + 'GLOBAL_VARIABLE' => \getenv('GLOBAL_VARIABLE') ?: '' ]); };