From 69ce99d1b43dd9cb5e33d17136cad689f95090c2 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 31 Dec 2025 11:52:41 +0530 Subject: [PATCH 1/2] feat: console module. --- app/config/services.php | 6 +- app/controllers/api/console.php | 147 ------------------ app/controllers/web/console.php | 43 ----- .../Modules/Console/Http/Assistant/Create.php | 92 +++++++++++ .../Modules/Console/Http/Init/API.php | 28 ++++ .../Modules/Console/Http/Init/Web.php | 31 ++++ .../Console/Http/Redirects/Auth/Get.php | 18 +++ .../Modules/Console/Http/Redirects/Base.php | 51 ++++++ .../Console/Http/Redirects/Card/Get.php | 18 +++ .../Console/Http/Redirects/Invite/Get.php | 18 +++ .../Console/Http/Redirects/Login/Get.php | 18 +++ .../Console/Http/Redirects/MFA/Get.php | 18 +++ .../Console/Http/Redirects/Recover/Get.php | 18 +++ .../Console/Http/Redirects/Register/Get.php | 18 +++ .../Console/Http/Redirects/Root/Get.php | 18 +++ .../Modules/Console/Http/Variables/Get.php | 93 +++++++++++ .../Modules/Console/Services/Http.php | 30 +++- 17 files changed, 471 insertions(+), 194 deletions(-) delete mode 100644 app/controllers/api/console.php delete mode 100644 app/controllers/web/console.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Assistant/Create.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Init/API.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Init/Web.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Base.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Card/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Invite/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Login/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/MFA/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Recover/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Register/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Redirects/Root/Get.php create mode 100644 src/Appwrite/Platform/Modules/Console/Http/Variables/Get.php diff --git a/app/config/services.php b/app/config/services.php index 8c3a20cf15..e4bbf9b6f6 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -20,7 +20,7 @@ return [ 'name' => 'Console', 'subtitle' => '', 'description' => '', - 'controller' => 'web/console.php', + 'controller' => '', // Uses modules 'sdk' => false, 'docs' => false, 'docsUrl' => '', @@ -270,9 +270,9 @@ return [ 'console' => [ 'key' => 'console', 'name' => 'Console', - 'subtitle' => 'The Console service allows you to interact with console relevant informations.', + 'subtitle' => 'The Console service allows you to interact with console relevant information.', 'description' => '', - 'controller' => 'api/console.php', + 'controller' => '', // Uses modules 'sdk' => true, 'docs' => true, 'docsUrl' => '', diff --git a/app/controllers/api/console.php b/app/controllers/api/console.php deleted file mode 100644 index 5bc8325794..0000000000 --- a/app/controllers/api/console.php +++ /dev/null @@ -1,147 +0,0 @@ -groups(['console']) - ->inject('project') - ->action(function (Document $project) { - if ($project->getId() !== 'console') { - throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN); - } - }); - - -App::get('/v1/console/variables') - ->desc('Get variables') - ->groups(['api', 'projects']) - ->label('scope', 'projects.read') - ->label('sdk', new Method( - namespace: 'console', - group: 'console', - name: 'variables', - description: '/docs/references/console/variables.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_CONSOLE_VARIABLES, - ) - ], - contentType: ContentType::JSON - )) - ->inject('response') - ->action(function (Response $response) { - $validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME')); - $isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest(); - - $validator = new IP(IP::V4); - $isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))); - - $validator = new IP(IP::V6); - $isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA')); - - $isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid; - - $isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', '')) - && !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', '')) - && !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', '')) - && !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', '')) - && !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', '')); - - $isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', '')); - - $variables = new Document([ - '_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'), - '_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'), - '_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'), - // Combine CAA domain with most common flags and tag (no parameters) - '_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"', - '_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'), - '_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), - '_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'), - '_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'), - '_APP_VCS_ENABLED' => $isVcsEnabled, - '_APP_DOMAIN_ENABLED' => $isDomainEnabled, - '_APP_ASSISTANT_ENABLED' => $isAssistantEnabled, - '_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'), - '_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'), - '_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'), - '_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'), - ]); - - $response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES); - }); - -App::post('/v1/console/assistant') - ->desc('Create assistant query') - ->groups(['api', 'assistant']) - ->label('scope', 'assistant.read') - ->label('sdk', new Method( - namespace: 'assistant', - group: 'console', - name: 'chat', - description: '/docs/references/assistant/chat.md', - auth: [AuthType::ADMIN], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_NONE, - ) - ], - contentType: ContentType::TEXT - )) - ->label('abuse-limit', 15) - ->label('abuse-key', 'userId:{userId}') - ->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.') - ->inject('response') - ->action(function (string $prompt, Response $response) { - $ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt'); - $responseHeaders = []; - $query = json_encode(['prompt' => $prompt]); - $headers = ['accept: text/event-stream']; - $handleEvent = function ($ch, $data) use ($response) { - $response->chunk($data); - - return \strlen($data); - }; - - curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent); - - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); - curl_setopt($ch, CURLOPT_TIMEOUT, 9000); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { - $len = strlen($header); - $header = explode(':', $header, 2); - - if (count($header) < 2) { // ignore invalid headers - return $len; - } - - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - - return $len; - }); - - curl_setopt($ch, CURLOPT_POSTFIELDS, $query); - - curl_exec($ch); - - curl_close($ch); - - $response->chunk('', true); - }); diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php deleted file mode 100644 index c02e140270..0000000000 --- a/app/controllers/web/console.php +++ /dev/null @@ -1,43 +0,0 @@ -groups(['web']) - ->inject('request') - ->inject('response') - ->action(function (Request $request, Response $response) { - $response - ->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes - ->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI())) - ->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode - ; - }); - -App::get('/') - ->alias('auth/*') - ->alias('/invite') - ->alias('/login') - ->alias('/mfa') - ->alias('/card/*') - ->alias('/recover') - ->alias('/register/*') - ->groups(['web']) - ->label('permission', 'public') - ->label('scope', 'home') - ->inject('request') - ->inject('response') - ->action(function (Request $request, Response $response) { - $url = parse_url($request->getURI()); - $target = "/console{$url['path']}"; - $params = $request->getParams(); - if (!empty($params)) { - $target .= "?" . \http_build_query($params); - } - if ($url['fragment'] ?? false) { - $target .= "#{$url['fragment']}"; - } - $response->redirect($target); - }); diff --git a/src/Appwrite/Platform/Modules/Console/Http/Assistant/Create.php b/src/Appwrite/Platform/Modules/Console/Http/Assistant/Create.php new file mode 100644 index 0000000000..554456b041 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Console/Http/Assistant/Create.php @@ -0,0 +1,92 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/console/assistant') + ->desc('Create assistant query') + ->groups(['api', 'assistant']) + ->label('scope', 'assistant.read') + ->label('sdk', new Method( + namespace: 'assistant', + group: 'console', + name: 'chat', + description: '/docs/references/assistant/chat.md', + auth: [AuthType::ADMIN], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_NONE, + ) + ], + contentType: ContentType::TEXT + )) + ->label('abuse-limit', 15) + ->label('abuse-key', 'userId:{userId}') + ->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.') + ->inject('response') + ->callback($this->action(...)); + } + + public function action(string $prompt, Response $response) + { + $ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt'); + $responseHeaders = []; + $query = json_encode(['prompt' => $prompt]); + $headers = ['accept: text/event-stream']; + $handleEvent = function ($ch, $data) use ($response) { + $response->chunk($data); + + return \strlen($data); + }; + + curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent); + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); + curl_setopt($ch, CURLOPT_TIMEOUT, 9000); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + $len = strlen($header); + $header = explode(':', $header, 2); + + if (count($header) < 2) { // ignore invalid headers + return $len; + } + + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); + + return $len; + }); + + curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + + curl_exec($ch); + + curl_close($ch); + + $response->chunk('', true); + } +} diff --git a/src/Appwrite/Platform/Modules/Console/Http/Init/API.php b/src/Appwrite/Platform/Modules/Console/Http/Init/API.php new file mode 100644 index 0000000000..824ef4c3d5 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Console/Http/Init/API.php @@ -0,0 +1,28 @@ +setType(Action::TYPE_INIT) + ->groups(['console']) + ->inject('project') + ->callback(function (Document $project) { + if ($project->getId() !== 'console') { + throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN); + } + }); + } +} diff --git a/src/Appwrite/Platform/Modules/Console/Http/Init/Web.php b/src/Appwrite/Platform/Modules/Console/Http/Init/Web.php new file mode 100644 index 0000000000..587610883a --- /dev/null +++ b/src/Appwrite/Platform/Modules/Console/Http/Init/Web.php @@ -0,0 +1,31 @@ +setType(Action::TYPE_INIT) + ->groups(['web']) + ->inject('request') + ->inject('response') + ->callback(function (Request $request, Response $response) { + $response + ->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes + ->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI())) + ->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode + ; + }); + } +} diff --git a/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php b/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php new file mode 100644 index 0000000000..9bce88ef92 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php @@ -0,0 +1,18 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath($this->getPath()) + ->groups(['web']) + ->label('permission', 'public') + ->label('scope', 'home') + ->inject('request') + ->inject('response') + ->callback($this->action(...)); + } + + public function action(Request $request, Response $response): void + { + $url = parse_url($request->getURI()); + $target = "/console{$url['path']}"; + $params = $request->getParams(); + if (!empty($params)) { + $target .= "?" . \http_build_query($params); + } + if ($url['fragment'] ?? false) { + $target .= "#{$url['fragment']}"; + } + + $response->redirect($target); + } +} diff --git a/src/Appwrite/Platform/Modules/Console/Http/Redirects/Card/Get.php b/src/Appwrite/Platform/Modules/Console/Http/Redirects/Card/Get.php new file mode 100644 index 0000000000..c98c125f4e --- /dev/null +++ b/src/Appwrite/Platform/Modules/Console/Http/Redirects/Card/Get.php @@ -0,0 +1,18 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/console/variables') + ->desc('Get variables') + ->groups(['api', 'projects']) + ->label('scope', 'projects.read') + ->label('sdk', new Method( + namespace: 'console', + group: 'console', + name: 'variables', + description: '/docs/references/console/variables.md', + auth: [AuthType::ADMIN], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_CONSOLE_VARIABLES, + ) + ], + contentType: ContentType::JSON + )) + ->inject('response') + ->callback($this->action(...)); + } + + public function action(Response $response) + { + $validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME')); + $isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest(); + + $validator = new IP(IP::V4); + $isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))); + + $validator = new IP(IP::V6); + $isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA')); + + $isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid; + + $isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', '')) + && !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', '')) + && !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', '')) + && !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', '')) + && !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', '')); + + $isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', '')); + + $variables = new Document([ + '_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'), + '_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'), + '_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'), + '_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"', + '_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'), + '_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'), + '_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'), + '_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'), + '_APP_VCS_ENABLED' => $isVcsEnabled, + '_APP_DOMAIN_ENABLED' => $isDomainEnabled, + '_APP_ASSISTANT_ENABLED' => $isAssistantEnabled, + '_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'), + '_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'), + '_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'), + '_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'), + ]); + + $response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES); + } +} diff --git a/src/Appwrite/Platform/Modules/Console/Services/Http.php b/src/Appwrite/Platform/Modules/Console/Services/Http.php index 6221db6a96..f3ca6218f2 100644 --- a/src/Appwrite/Platform/Modules/Console/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Console/Services/Http.php @@ -2,7 +2,19 @@ namespace Appwrite\Platform\Modules\Console\Services; +use Appwrite\Platform\Modules\Console\Http\Assistant\Create as CreateAssistantQuery; +use Appwrite\Platform\Modules\Console\Http\Init\API; +use Appwrite\Platform\Modules\Console\Http\Init\Web; +use Appwrite\Platform\Modules\Console\Http\Redirects\Auth\Get as RedirectAuth; +use Appwrite\Platform\Modules\Console\Http\Redirects\Card\Get as RedirectCard; +use Appwrite\Platform\Modules\Console\Http\Redirects\Invite\Get as RedirectInvite; +use Appwrite\Platform\Modules\Console\Http\Redirects\Login\Get as RedirectLogin; +use Appwrite\Platform\Modules\Console\Http\Redirects\MFA\Get as RedirectMFA; +use Appwrite\Platform\Modules\Console\Http\Redirects\Recover\Get as RedirectRecover; +use Appwrite\Platform\Modules\Console\Http\Redirects\Register\Get as RedirectRegister; +use Appwrite\Platform\Modules\Console\Http\Redirects\Root\Get as RedirectRoot; use Appwrite\Platform\Modules\Console\Http\Resources\Get as GetResourceAvailability; +use Appwrite\Platform\Modules\Console\Http\Variables\Get as GetVariables; use Utopia\Platform\Service; class Http extends Service @@ -10,7 +22,23 @@ class Http extends Service public function __construct() { $this->type = Service::TYPE_HTTP; - // Resources + + // API and Web init hooks! + $this->addAction(API::getName(), new API()); + $this->addAction(Web::getName(), new Web()); + + $this->addAction(GetVariables::getName(), new GetVariables()); + $this->addAction(CreateAssistantQuery::getName(), new CreateAssistantQuery()); $this->addAction(GetResourceAvailability::getName(), new GetResourceAvailability()); + + // web redirects to /console + $this->addAction(RedirectRoot::getName(), new RedirectRoot()); + $this->addAction(RedirectAuth::getName(), new RedirectAuth()); + $this->addAction(RedirectInvite::getName(), new RedirectInvite()); + $this->addAction(RedirectLogin::getName(), new RedirectLogin()); + $this->addAction(RedirectMFA::getName(), new RedirectMFA()); + $this->addAction(RedirectCard::getName(), new RedirectCard()); + $this->addAction(RedirectRecover::getName(), new RedirectRecover()); + $this->addAction(RedirectRegister::getName(), new RedirectRegister()); } } From 2e57e5a868d33ff6c51cc83c6d1a7338ce7248e5 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 31 Dec 2025 11:59:01 +0530 Subject: [PATCH 2/2] fix: url. --- .../Platform/Modules/Console/Http/Redirects/Auth/Get.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php b/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php index 9bce88ef92..f88486d6bb 100644 --- a/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php +++ b/src/Appwrite/Platform/Modules/Console/Http/Redirects/Auth/Get.php @@ -13,6 +13,6 @@ class Get extends Base protected function getPath(): string { - return 'auth/*'; + return '/auth/*'; } }