From 26a822e4b2881f0e910253b094ca2b9e72482112 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 27 Feb 2025 18:53:11 +0530 Subject: [PATCH] Move all function endpoints to modules structure --- app/controllers/api/functions.php | 446 ------------------ .../Functions/Http/Executions/Delete.php | 116 +++++ .../Modules/Functions/Http/Templates/Get.php | 70 +++ .../Functions/Http/Templates/XList.php | 80 ++++ .../Functions/Http/Variables/Create.php | 115 +++++ .../Functions/Http/Variables/Delete.php | 93 ++++ .../Modules/Functions/Http/Variables/Get.php | 82 ++++ .../Functions/Http/Variables/Update.php | 111 +++++ .../Functions/Http/Variables/XList.php | 71 +++ .../Modules/Functions/Services/Http.php | 20 + .../Modules/Sites/Http/Variables/Delete.php | 1 + .../Modules/Sites/Http/Variables/Get.php | 1 + .../Modules/Sites/Http/Variables/Update.php | 1 + 13 files changed, 761 insertions(+), 446 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Templates/Get.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Variables/Get.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php create mode 100644 src/Appwrite/Platform/Modules/Functions/Http/Variables/XList.php diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 6f76ef2420..edf673745e 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1,450 +1,4 @@ groups(['api', 'functions']) - ->desc('Delete execution') - ->label('scope', 'execution.write') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('event', 'functions.[functionId].executions.[executionId].delete') - ->label('audits.event', 'executions.delete') - ->label('audits.resource', 'function/{request.functionId}') - ->label('sdk', new Method( - namespace: 'functions', - name: 'deleteExecution', - description: '/docs/references/functions/delete-execution.md', - auth: [AuthType::KEY], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_NOCONTENT, - model: Response::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - )) - ->param('functionId', '', new UID(), 'Function ID.') - ->param('executionId', '', new UID(), 'Execution ID.') - ->inject('response') - ->inject('dbForProject') - ->inject('dbForPlatform') - ->inject('queueForEvents') - ->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents) { - $function = $dbForProject->getDocument('functions', $functionId); - - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $execution = $dbForProject->getDocument('executions', $executionId); - if ($execution->isEmpty()) { - throw new Exception(Exception::EXECUTION_NOT_FOUND); - } - - if ($execution->getAttribute('resourceType') !== 'functions' && $execution->getAttribute('resourceInternalId') !== $function->getInternalId()) { - throw new Exception(Exception::EXECUTION_NOT_FOUND); - } - $status = $execution->getAttribute('status'); - - if (!in_array($status, ['completed', 'failed', 'scheduled'])) { - throw new Exception(Exception::EXECUTION_IN_PROGRESS); - } - - if (!$dbForProject->deleteDocument('executions', $execution->getId())) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove execution from DB'); - } - - if ($status === 'scheduled') { - $schedule = $dbForPlatform->findOne('schedules', [ - Query::equal('resourceId', [$execution->getId()]), - Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]), - Query::equal('active', [true]), - ]); - - if (!$schedule->isEmpty()) { - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('active', false); - - Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); - } - } - - $queueForEvents - ->setParam('functionId', $function->getId()) - ->setParam('executionId', $execution->getId()) - ->setPayload($response->output($execution, Response::MODEL_EXECUTION)); - - $response->noContent(); - }); - -// Variables - -App::post('/v1/functions/:functionId/variables') - ->desc('Create variable') - ->groups(['api', 'functions']) - ->label('scope', 'functions.write') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('audits.event', 'variable.create') - ->label('audits.resource', 'function/{request.functionId}') - ->label('sdk', new Method( - namespace: 'functions', - name: 'createVariable', - description: '/docs/references/functions/create-variable.md', - auth: [AuthType::KEY], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_VARIABLE, - ) - ] - )) - ->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) - ->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('dbForPlatform') - ->action(function (string $functionId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) { - $function = $dbForProject->getDocument('functions', $functionId); - - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $variableId = ID::unique(); - - $variable = new Document([ - '$id' => $variableId, - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'resourceInternalId' => $function->getInternalId(), - 'resourceId' => $function->getId(), - 'resourceType' => 'function', - 'key' => $key, - 'value' => $value, - 'secret' => $secret, - 'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']), - ]); - - try { - $variable = $dbForProject->createDocument('variables', $variable); - } catch (DuplicateException $th) { - throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); - } - - $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); - - // Inform scheduler to pull the latest changes - $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('schedule', $function->getAttribute('schedule')) - ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($variable, Response::MODEL_VARIABLE); - }); - -App::get('/v1/functions/:functionId/variables') - ->desc('List variables') - ->groups(['api', 'functions']) - ->label('scope', 'functions.read') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label( - 'sdk', - new Method( - namespace: 'functions', - name: 'listVariables', - description: '/docs/references/functions/list-variables.md', - auth: [AuthType::KEY], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_VARIABLE_LIST, - ) - ], - ) - ) - ->param('functionId', '', new UID(), 'Function unique ID.', false) - ->inject('response') - ->inject('dbForProject') - ->action(function (string $functionId, Response $response, Database $dbForProject) { - $function = $dbForProject->getDocument('functions', $functionId); - - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $response->dynamic(new Document([ - 'variables' => $function->getAttribute('vars', []), - 'total' => \count($function->getAttribute('vars', [])), - ]), Response::MODEL_VARIABLE_LIST); - }); - -App::get('/v1/functions/:functionId/variables/:variableId') - ->desc('Get variable') - ->groups(['api', 'functions']) - ->label('scope', 'functions.read') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'functions') - ->label('sdk.method', 'getVariable') - ->label('sdk.description', '/docs/references/functions/get-variable.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', '', new UID(), 'Function unique ID.', false) - ->param('variableId', '', new UID(), 'Variable unique ID.', false) - ->inject('response') - ->inject('dbForProject') - ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) { - $function = $dbForProject->getDocument('functions', $functionId); - - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $variable = $dbForProject->getDocument('variables', $variableId); - if ( - $variable === false || - $variable->isEmpty() || - $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || - $variable->getAttribute('resourceType') !== 'function' - ) { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } - - if ($variable === false || $variable->isEmpty()) { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } - - $response->dynamic($variable, Response::MODEL_VARIABLE); - }); - -App::put('/v1/functions/:functionId/variables/:variableId') - ->desc('Update variable') - ->groups(['api', 'functions']) - ->label('scope', 'functions.write') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('audits.event', 'variable.update') - ->label('audits.resource', 'function/{request.functionId}') - ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'functions') - ->label('sdk.method', 'updateVariable') - ->label('sdk.description', '/docs/references/functions/update-variable.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_VARIABLE) - ->param('functionId', '', new UID(), 'Function unique ID.', false) - ->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) - ->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('dbForPlatform') - ->action(function (string $functionId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) { - - $function = $dbForProject->getDocument('functions', $functionId); - - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $variable = $dbForProject->getDocument('variables', $variableId); - if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } - - if ($variable === false || $variable->isEmpty()) { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } - - if ($variable->getAttribute('secret') === true && $secret === false) { - throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET); - } - - $variable - ->setAttribute('key', $key) - ->setAttribute('value', $value ?? $variable->getAttribute('value')) - ->setAttribute('secret', $secret ?? $variable->getAttribute('secret')) - ->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function'])); - - try { - $dbForProject->updateDocument('variables', $variable->getId(), $variable); - } catch (DuplicateException $th) { - throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); - } - - $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); - - // Inform scheduler to pull the latest changes - $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('schedule', $function->getAttribute('schedule')) - ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); - - $response->dynamic($variable, Response::MODEL_VARIABLE); - }); - -App::delete('/v1/functions/:functionId/variables/:variableId') - ->desc('Delete variable') - ->groups(['api', 'functions']) - ->label('scope', 'functions.write') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('audits.event', 'variable.delete') - ->label('audits.resource', 'function/{request.functionId}') - ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) - ->label('sdk.namespace', 'functions') - ->label('sdk.method', 'deleteVariable') - ->label('sdk.description', '/docs/references/functions/delete-variable.md') - ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) - ->label('sdk.response.model', Response::MODEL_NONE) - ->param('functionId', '', new UID(), 'Function unique ID.', false) - ->param('variableId', '', new UID(), 'Variable unique ID.', false) - ->inject('response') - ->inject('dbForProject') - ->inject('dbForPlatform') - ->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForPlatform) { - $function = $dbForProject->getDocument('functions', $functionId); - - if ($function->isEmpty()) { - throw new Exception(Exception::FUNCTION_NOT_FOUND); - } - - $variable = $dbForProject->getDocument('variables', $variableId); - if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } - - if ($variable === false || $variable->isEmpty()) { - throw new Exception(Exception::VARIABLE_NOT_FOUND); - } - - $dbForProject->deleteDocument('variables', $variable->getId()); - - $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); - - // Inform scheduler to pull the latest changes - $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); - $schedule - ->setAttribute('resourceUpdatedAt', DateTime::now()) - ->setAttribute('schedule', $function->getAttribute('schedule')) - ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); - Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); - - $response->noContent(); - }); - -App::get('/v1/functions/templates') - ->groups(['api']) - ->desc('List function templates') - ->label('scope', 'public') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('sdk', new Method( - namespace: 'functions', - name: 'listTemplates', - description: '/docs/references/functions/list-templates.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_TEMPLATE_FUNCTION_LIST, - ) - ] - )) - ->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) - ->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true) - ->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true) - ->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true) - ->inject('response') - ->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) { - $templates = Config::getParam('function-templates', []); - - if (!empty($runtimes)) { - $templates = \array_filter($templates, function ($template) use ($runtimes) { - return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0; - }); - } - - if (!empty($usecases)) { - $templates = \array_filter($templates, function ($template) use ($usecases) { - return \count(\array_intersect($usecases, $template['useCases'])) > 0; - }); - } - - $responseTemplates = \array_slice($templates, $offset, $limit); - $response->dynamic(new Document([ - 'templates' => $responseTemplates, - 'total' => \count($responseTemplates), - ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); - }); - -App::get('/v1/functions/templates/:templateId') - ->desc('Get function template') - ->label('scope', 'public') - ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) - ->label('sdk', new Method( - namespace: 'functions', - name: 'getTemplate', - description: '/docs/references/functions/get-template.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_TEMPLATE_FUNCTION, - ) - ] - )) - ->param('templateId', '', new Text(128), 'Template ID.') - ->inject('response') - ->action(function (string $templateId, Response $response) { - $templates = Config::getParam('function-templates', []); - - $filtered = \array_filter($templates, function ($template) use ($templateId) { - return $template['id'] === $templateId; - }); - - $template = array_shift($filtered); - - if (empty($template)) { - throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); - } - - $response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION); - }); diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php new file mode 100644 index 0000000000..7b9fd77a25 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Executions/Delete.php @@ -0,0 +1,116 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE) + ->setHttpPath('/v1/functions/:functionId/executions/:executionId') + ->desc('Delete execution') + ->groups(['api', 'functions']) + ->label('scope', 'execution.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label('event', 'functions.[functionId].executions.[executionId].delete') + ->label('audits.event', 'executions.delete') + ->label('audits.resource', 'function/{request.functionId}') + ->label('sdk', new Method( + namespace: 'functions', + name: 'deleteExecution', + description: <<param('functionId', '', new UID(), 'Function ID.') + ->param('executionId', '', new UID(), 'Execution ID.') + ->inject('response') + ->inject('dbForProject') + ->inject('dbForPlatform') + ->inject('queueForEvents') + ->callback([$this, 'action']); + } + + public function action(string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents) + { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $execution = $dbForProject->getDocument('executions', $executionId); + if ($execution->isEmpty()) { + throw new Exception(Exception::EXECUTION_NOT_FOUND); + } + + if ($execution->getAttribute('resourceType') !== 'functions' && $execution->getAttribute('resourceInternalId') !== $function->getInternalId()) { + throw new Exception(Exception::EXECUTION_NOT_FOUND); + } + $status = $execution->getAttribute('status'); + + if (!in_array($status, ['completed', 'failed', 'scheduled'])) { + throw new Exception(Exception::EXECUTION_IN_PROGRESS); + } + + if (!$dbForProject->deleteDocument('executions', $execution->getId())) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove execution from DB'); + } + + if ($status === 'scheduled') { + $schedule = $dbForPlatform->findOne('schedules', [ + Query::equal('resourceId', [$execution->getId()]), + Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]), + Query::equal('active', [true]), + ]); + + if (!$schedule->isEmpty()) { + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('active', false); + + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + } + } + + $queueForEvents + ->setParam('functionId', $function->getId()) + ->setParam('executionId', $execution->getId()) + ->setPayload($response->output($execution, Response::MODEL_EXECUTION)); + + $response->noContent(); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Templates/Get.php b/src/Appwrite/Platform/Modules/Functions/Http/Templates/Get.php new file mode 100644 index 0000000000..4b84f777de --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Templates/Get.php @@ -0,0 +1,70 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/functions/templates/:templateId') + ->desc('Get function template') + ->groups(['api']) + ->label('scope', 'public') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label('sdk', new Method( + namespace: 'functions', + name: 'getTemplate', + description: <<param('templateId', '', new Text(128), 'Template ID.') + ->inject('response') + ->callback([$this, 'action']); + } + + public function action(string $templateId, Response $response) + { + $templates = Config::getParam('function-templates', []); + + $filtered = \array_filter($templates, function ($template) use ($templateId) { + return $template['id'] === $templateId; + }); + + $template = array_shift($filtered); + + if (empty($template)) { + throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); + } + + $response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php b/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php new file mode 100644 index 0000000000..ebf9ee28d4 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Templates/XList.php @@ -0,0 +1,80 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/functions/templates') + ->desc('List function templates') + ->groups(['api']) + ->label('scope', 'public') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label('sdk', new Method( + namespace: 'functions', + name: 'listTemplates', + description: <<param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) + ->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true) + ->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true) + ->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true) + ->inject('response') + ->callback([$this, 'action']); + } + + public function action(array $runtimes, array $usecases, int $limit, int $offset, Response $response) + { + $templates = Config::getParam('function-templates', []); + + if (!empty($runtimes)) { + $templates = \array_filter($templates, function ($template) use ($runtimes) { + return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0; + }); + } + + if (!empty($usecases)) { + $templates = \array_filter($templates, function ($template) use ($usecases) { + return \count(\array_intersect($usecases, $template['useCases'])) > 0; + }); + } + + $responseTemplates = \array_slice($templates, $offset, $limit); + $response->dynamic(new Document([ + 'templates' => $responseTemplates, + 'total' => \count($responseTemplates), + ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php new file mode 100644 index 0000000000..ba48a5c4bc --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Create.php @@ -0,0 +1,115 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/functions/:functionId/variables') + ->desc('Create variable') + ->groups(['api', 'functions']) + ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label('audits.event', 'variable.create') + ->label('audits.resource', 'function/{request.functionId}') + ->label('sdk', new Method( + namespace: 'functions', + name: 'createVariable', + description: <<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) + ->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true) + ->inject('response') + ->inject('dbForProject') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $functionId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) + { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $variableId = ID::unique(); + + $variable = new Document([ + '$id' => $variableId, + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'resourceInternalId' => $function->getInternalId(), + 'resourceId' => $function->getId(), + 'resourceType' => 'function', + 'key' => $key, + 'value' => $value, + 'secret' => $secret, + 'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']), + ]); + + try { + $variable = $dbForProject->createDocument('variables', $variable); + } catch (DuplicateException $th) { + throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); + } + + $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); + + // Inform scheduler to pull the latest changes + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('schedule', $function->getAttribute('schedule')) + ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($variable, Response::MODEL_VARIABLE); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php new file mode 100644 index 0000000000..650eaf4010 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Delete.php @@ -0,0 +1,93 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE) + ->setHttpPath('/v1/functions/:functionId/variables/:variableId') + ->desc('Delete variable') + ->groups(['api', 'functions']) + ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label('audits.event', 'variable.delete') + ->label('audits.resource', 'function/{request.functionId}') + ->label('sdk', new Method( + namespace: 'functions', + name: 'deleteVariable', + description: <<param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) + ->inject('response') + ->inject('dbForProject') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForPlatform) + { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $variable = $dbForProject->getDocument('variables', $variableId); + if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') { + throw new Exception(Exception::VARIABLE_NOT_FOUND); + } + + if ($variable === false || $variable->isEmpty()) { + throw new Exception(Exception::VARIABLE_NOT_FOUND); + } + + $dbForProject->deleteDocument('variables', $variable->getId()); + + $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); + + // Inform scheduler to pull the latest changes + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('schedule', $function->getAttribute('schedule')) + ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + + $response->noContent(); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Variables/Get.php b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Get.php new file mode 100644 index 0000000000..eeb5c51f33 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Get.php @@ -0,0 +1,82 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/functions/:functionId/variables/:variableId') + ->desc('Get variable') + ->groups(['api', 'functions']) + ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label( + 'sdk', + new Method( + namespace: 'functions', + name: 'getVariable', + description: <<param('functionId', '', new UID(), 'Function unique ID.', false) + ->param('variableId', '', new UID(), 'Variable unique ID.', false) + ->inject('response') + ->inject('dbForProject') + ->callback([$this, 'action']); + } + + public function action(string $functionId, string $variableId, Response $response, Database $dbForProject) + { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $variable = $dbForProject->getDocument('variables', $variableId); + if ( + $variable === false || + $variable->isEmpty() || + $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || + $variable->getAttribute('resourceType') !== 'function' + ) { + throw new Exception(Exception::VARIABLE_NOT_FOUND); + } + + if ($variable === false || $variable->isEmpty()) { + throw new Exception(Exception::VARIABLE_NOT_FOUND); + } + + $response->dynamic($variable, Response::MODEL_VARIABLE); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php new file mode 100644 index 0000000000..8911dc8abb --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Variables/Update.php @@ -0,0 +1,111 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) + ->setHttpPath('/v1/functions/:functionId/variables/:variableId') + ->desc('Update variable') + ->groups(['api', 'functions']) + ->label('scope', 'functions.write') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label('audits.event', 'variable.update') + ->label('audits.resource', 'function/{request.functionId}') + ->label('sdk', new Method( + namespace: 'functions', + name: 'updateVariable', + description: <<param('functionId', '', new UID(), 'Function unique ID.', false) + ->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) + ->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true) + ->inject('response') + ->inject('dbForProject') + ->inject('dbForPlatform') + ->callback([$this, 'action']); + } + + public function action(string $functionId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) + { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $variable = $dbForProject->getDocument('variables', $variableId); + if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') { + throw new Exception(Exception::VARIABLE_NOT_FOUND); + } + + if ($variable === false || $variable->isEmpty()) { + throw new Exception(Exception::VARIABLE_NOT_FOUND); + } + + if ($variable->getAttribute('secret') === true && $secret === false) { + throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET); + } + + $variable + ->setAttribute('key', $key) + ->setAttribute('value', $value ?? $variable->getAttribute('value')) + ->setAttribute('secret', $secret ?? $variable->getAttribute('secret')) + ->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function'])); + + try { + $dbForProject->updateDocument('variables', $variable->getId(), $variable); + } catch (DuplicateException $th) { + throw new Exception(Exception::VARIABLE_ALREADY_EXISTS); + } + + $dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false)); + + // Inform scheduler to pull the latest changes + $schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')); + $schedule + ->setAttribute('resourceUpdatedAt', DateTime::now()) + ->setAttribute('schedule', $function->getAttribute('schedule')) + ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); + Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule)); + + $response->dynamic($variable, Response::MODEL_VARIABLE); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Variables/XList.php b/src/Appwrite/Platform/Modules/Functions/Http/Variables/XList.php new file mode 100644 index 0000000000..000e83a0c9 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Functions/Http/Variables/XList.php @@ -0,0 +1,71 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/functions/:functionId/variables') + ->desc('List variables') + ->groups(['api', 'functions']) + ->label('scope', 'functions.read') + ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) + ->label( + 'sdk', + new Method( + namespace: 'functions', + name: 'listVariables', + description: <<param('functionId', '', new UID(), 'Function unique ID.', false) + ->inject('response') + ->inject('dbForProject') + ->callback([$this, 'action']); + } + + public function action(string $functionId, Response $response, Database $dbForProject) + { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $response->dynamic(new Document([ + 'variables' => $function->getAttribute('vars', []), + 'total' => \count($function->getAttribute('vars', [])), + ]), Response::MODEL_VARIABLE_LIST); + } +} diff --git a/src/Appwrite/Platform/Modules/Functions/Services/Http.php b/src/Appwrite/Platform/Modules/Functions/Services/Http.php index f8c412db2a..5ae77c5d6b 100644 --- a/src/Appwrite/Platform/Modules/Functions/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Functions/Services/Http.php @@ -13,6 +13,7 @@ use Appwrite\Platform\Modules\Functions\Http\Deployments\Update as UpdateDeploym use Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs\Create as CreateVcsDeployment; use Appwrite\Platform\Modules\Functions\Http\Deployments\XList as ListDeployments; use Appwrite\Platform\Modules\Functions\Http\Executions\Create as CreateExecution; +use Appwrite\Platform\Modules\Functions\Http\Executions\Delete as DeleteExecution; use Appwrite\Platform\Modules\Functions\Http\Executions\Get as GetExecution; use Appwrite\Platform\Modules\Functions\Http\Executions\XList as ListExecutions; use Appwrite\Platform\Modules\Functions\Http\Functions\Create as CreateFunction; @@ -22,8 +23,15 @@ use Appwrite\Platform\Modules\Functions\Http\Functions\Update as UpdateFunction; use Appwrite\Platform\Modules\Functions\Http\Functions\XList as ListFunctions; use Appwrite\Platform\Modules\Functions\Http\Runtimes\XList as ListRuntimes; use Appwrite\Platform\Modules\Functions\Http\Specifications\XList as ListSpecifications; +use Appwrite\Platform\Modules\Functions\Http\Templates\Get as GetTemplate; +use Appwrite\Platform\Modules\Functions\Http\Templates\XList as ListTemplates; use Appwrite\Platform\Modules\Functions\Http\Usage\Get as GetUsage; use Appwrite\Platform\Modules\Functions\Http\Usage\XList as ListUsage; +use Appwrite\Platform\Modules\Functions\Http\Variables\Create as CreateVariable; +use Appwrite\Platform\Modules\Functions\Http\Variables\Delete as DeleteVariable; +use Appwrite\Platform\Modules\Functions\Http\Variables\Get as GetVariable; +use Appwrite\Platform\Modules\Functions\Http\Variables\Update as UpdateVariable; +use Appwrite\Platform\Modules\Functions\Http\Variables\XList as ListVariables; use Utopia\Platform\Service; class Http extends Service @@ -61,9 +69,21 @@ class Http extends Service $this->addAction(CreateExecution::getName(), new CreateExecution()); $this->addAction(GetExecution::getName(), new GetExecution()); $this->addAction(ListExecutions::getName(), new ListExecutions()); + $this->addAction(DeleteExecution::getName(), new DeleteExecution()); // Usage $this->addAction(GetUsage::getName(), new GetUsage()); $this->addAction(ListUsage::getName(), new ListUsage()); + + // Variables + $this->addAction(CreateVariable::getName(), new CreateVariable()); + $this->addAction(GetVariable::getName(), new GetVariable()); + $this->addAction(ListVariables::getName(), new ListVariables()); + $this->addAction(UpdateVariable::getName(), new UpdateVariable()); + $this->addAction(DeleteVariable::getName(), new DeleteVariable()); + + // Templates + $this->addAction(GetTemplate::getName(), new GetTemplate()); + $this->addAction(ListTemplates::getName(), new ListTemplates()); } } diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php index 461bcdfdd9..3689343831 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Delete.php @@ -31,6 +31,7 @@ class Delete extends Base ->desc('Delete variable') ->groups(['api', 'sites']) ->label('scope', 'sites.write') + ->label('resourceType', 'sites') ->label('audits.event', 'variable.delete') ->label('audits.resource', 'site/{request.siteId}') ->label('sdk', new Method( diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Get.php b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Get.php index ca5ad09663..1e56cc5b15 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Get.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Get.php @@ -30,6 +30,7 @@ class Get extends Base ->desc('Get variable') ->groups(['api', 'sites']) ->label('scope', 'sites.read') + ->label('resourceType', RESOURCE_TYPE_SITES) ->label( 'sdk', new Method( diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php index 4040a8ae71..d64af650e3 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php @@ -35,6 +35,7 @@ class Update extends Base ->label('scope', 'sites.write') ->label('audits.event', 'variable.update') ->label('audits.resource', 'site/{request.siteId}') + ->label('resourceType', RESOURCE_TYPE_SITES) ->label('sdk', new Method( namespace: 'sites', name: 'updateVariable',