From 3b5b709fb16819460dd89eea51025ed7a9b148cc Mon Sep 17 00:00:00 2001 From: Andrey Borysenko Date: Tue, 7 May 2024 14:04:34 +0300 Subject: [PATCH 1/4] WIP: add async requestToExApp, OCS API endpoints Signed-off-by: Andrey Borysenko --- appinfo/routes.php | 4 ++ docs/tech_details/api/exapp.rst | 43 +++++++++++++ lib/Controller/OCSExAppController.php | 51 +++++++++++++++ lib/PublicFunctions.php | 47 +++++++++++++- lib/Service/AppAPIService.php | 89 +++++++++++++++------------ 5 files changed, 194 insertions(+), 40 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index 9e08a6e2..fabe2598 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -64,6 +64,10 @@ ['name' => 'OCSExApp#getExAppsList', 'url' => '/api/v1/ex-app/{list}', 'verb' => 'GET'], ['name' => 'OCSExApp#getExApp', 'url' => '/api/v1/ex-app/info/{appId}', 'verb' => 'GET'], + // Requests to ExApps + ['name' => 'OCSExApp#requestToExApp', 'url' => '/api/v1/ex-app/request/{appId}/', 'verb' => 'POST'], + ['name' => 'OCSExApp#aeRequestToExApp', 'url' => '/api/v1/ex-app/request/{appId}/{$userId}', 'verb' => 'POST'], + // ExApps actions ['name' => 'OCSExApp#setExAppEnabled', 'url' => '/api/v1/ex-app/{appId}/enabled', 'verb' => 'PUT'], diff --git a/docs/tech_details/api/exapp.rst b/docs/tech_details/api/exapp.rst index dbd987ad..ce46af67 100644 --- a/docs/tech_details/api/exapp.rst +++ b/docs/tech_details/api/exapp.rst @@ -32,3 +32,46 @@ The response data is a JSON array of ExApp objects with the following attributes "last_check_time": "timestamp of last successful Nextcloud->ExApp connection check", "system": "true/false flag indicating system ExApp", } + + +Make Requests to ExApps +^^^^^^^^^^^^^^^^^^^^^^^ + +There are two endpoints for making requests to ExApps: + +1. Synchronous request: ``POST /apps/app_api/api/v1/ex-app/request/{appid}`` +2. Synchronous request with ExApp user setup: ``POST /apps/app_api/api/v1/ex-app/request/{appid}/{userId}`` + +Request data +************ + +The request data params are the same as in ``lib/PublicFunction.php``: + +.. code-block:: json + + { + "route": "relative route to ExApp API endpoint", + "method": "GET/POST/PUT/DELETE", + "params": {}, + "options": {}, + } + +.. note:: + + ``userId`` and ``appId`` is taken from url params + + +Response data +************* + +Successful request to ExApp OCS data response structure is the following: + +.. code-block:: json + + { + "status_code": "HTTP status code", + "body": "response data from ExApp", + "headers": "response headers from ExApp", + } + +If there is an error, the response object will have only an ``error`` attribute with the error message. diff --git a/lib/Controller/OCSExAppController.php b/lib/Controller/OCSExAppController.php index d68d7c02..de7730e4 100644 --- a/lib/Controller/OCSExAppController.php +++ b/lib/Controller/OCSExAppController.php @@ -16,6 +16,7 @@ use OCP\IRequest; class OCSExAppController extends OCSController { + protected $request; public function __construct( IRequest $request, @@ -23,6 +24,8 @@ public function __construct( private readonly ExAppService $exAppService, ) { parent::__construct(Application::APP_ID, $request); + + $this->request = $request; } #[NoCSRFRequired] @@ -70,4 +73,52 @@ public function setExAppEnabled(string $appId, int $enabled): DataResponse { return new DataResponse(); } + + #[NoCSRFRequired] + public function requestToExApp( + string $appId, + string $route, + ?string $userId = null, + string $method = 'POST', + array $params = [], + array $options = [], + ): DataResponse { + $exApp = $this->exAppService->getExApp($appId); + if ($exApp === null) { + return new DataResponse(['error' => sprintf('ExApp `%s` not found', $appId)]); + } + $response = $this->service->requestToExApp($exApp, $route, $userId, $method, $params, $options, $this->request); + if (is_array($response) && isset($response['error'])) { + return new DataResponse($response, Http::STATUS_BAD_REQUEST); + } + return new DataResponse([ + 'status_code' => $response->getStatusCode(), + 'headers' => $response->getHeaders(), + 'body' => $response->getBody(), + ]); + } + + #[NoCSRFRequired] + public function aeRequestToExApp( + string $appId, + string $route, + ?string $userId = null, + string $method = 'POST', + array $params = [], + array $options = [], + ): DataResponse { + $exApp = $this->exAppService->getExApp($appId); + if ($exApp === null) { + return new DataResponse(['error' => sprintf('ExApp `%s` not found', $appId)]); + } + $response = $this->service->aeRequestToExApp($exApp, $route, $userId, $method, $params, $options, $this->request); + if (is_array($response) && isset($response['error'])) { + return new DataResponse($response, Http::STATUS_BAD_REQUEST); + } + return new DataResponse([ + 'status_code' => $response->getStatusCode(), + 'headers' => $response->getHeaders(), + 'body' => $response->getBody(), + ]); + } } diff --git a/lib/PublicFunctions.php b/lib/PublicFunctions.php index 7986e518..22a17b2d 100644 --- a/lib/PublicFunctions.php +++ b/lib/PublicFunctions.php @@ -6,6 +6,7 @@ use OCA\AppAPI\Service\AppAPIService; use OCA\AppAPI\Service\ExAppService; +use OCP\Http\Client\IPromise; use OCP\Http\Client\IResponse; use OCP\IRequest; @@ -28,7 +29,7 @@ public function exAppRequest( array $params = [], array $options = [], ?IRequest $request = null, - ): array|IResponse { + ): array|IResponse { $exApp = $this->exAppService->getExApp($appId); if ($exApp === null) { return ['error' => sprintf('ExApp `%s` not found', $appId)]; @@ -47,11 +48,53 @@ public function exAppRequestWithUserInit( array $params = [], array $options = [], ?IRequest $request = null, - ): array|IResponse { + ): array|IResponse { $exApp = $this->exAppService->getExApp($appId); if ($exApp === null) { return ['error' => sprintf('ExApp `%s` not found', $appId)]; } return $this->service->aeRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request); } + + /** + * Async request to ExApp with AppAPI auth headers + * + * @throws \Exception if ExApp not found + */ + public function asyncExAppRequest( + string $appId, + string $route, + ?string $userId = null, + string $method = 'POST', + array $params = [], + array $options = [], + ?IRequest $request = null, + ): array|IPromise { + $exApp = $this->exAppService->getExApp($appId); + if ($exApp === null) { + throw new \Exception(sprintf('ExApp `%s` not found', $appId)); + } + return $this->service->requestToExAppAsync($exApp, $route, $userId, $method, $params, $options, $request); + } + + /** + * Async request to ExApp with AppAPI auth headers and ExApp user initialization + * + * @throws \Exception if ExApp not found or failed to setup ExApp user + */ + public function asyncExAppRequestWithUserInit( + string $appId, + string $route, + ?string $userId = null, + string $method = 'POST', + array $params = [], + array $options = [], + ?IRequest $request = null, + ): array|IPromise { + $exApp = $this->exAppService->getExApp($appId); + if ($exApp === null) { + throw new \Exception(sprintf('ExApp `%s` not found', $appId)); + } + return $this->service->aeRequestToExAppAsync($exApp, $route, $userId, $method, $params, $options, $request); + } } diff --git a/lib/Service/AppAPIService.php b/lib/Service/AppAPIService.php index 01f7feaf..97548cbd 100644 --- a/lib/Service/AppAPIService.php +++ b/lib/Service/AppAPIService.php @@ -14,6 +14,7 @@ use OCP\DB\Exception; use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; +use OCP\Http\Client\IPromise; use OCP\Http\Client\IResponse; use OCP\IConfig; use OCP\IRequest; @@ -66,7 +67,7 @@ public function aeRequestToExApp( try { $this->exAppUsersService->setupExAppUser($exApp->getAppid(), $userId); } catch (\Exception $e) { - $this->logger->error(sprintf('Error while inserting ExApp %s user. Error: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); + $this->logger->warning(sprintf('Error while inserting ExApp %s user. Error: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); return ['error' => 'Error while inserting ExApp user: ' . $e->getMessage()]; } return $this->requestToExApp($exApp, $route, $userId, $method, $params, $options, $request); @@ -96,29 +97,44 @@ private function requestToExAppInternal( array $options, ): array|IResponse { try { - switch ($method) { - case 'GET': - $response = $this->client->get($uri, $options); - break; - case 'POST': - $response = $this->client->post($uri, $options); - break; - case 'PUT': - $response = $this->client->put($uri, $options); - break; - case 'DELETE': - $response = $this->client->delete($uri, $options); - break; - default: - return ['error' => 'Bad HTTP method']; - } - return $response; + return match ($method) { + 'GET' => $this->client->get($uri, $options), + 'POST' => $this->client->post($uri, $options), + 'PUT' => $this->client->put($uri, $options), + 'DELETE' => $this->client->delete($uri, $options), + default => ['error' => 'Bad HTTP method'], + }; } catch (\Exception $e) { - $this->logger->error(sprintf('Error during request to ExApp %s: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); + $this->logger->warning(sprintf('Error during request to ExApp %s: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); return ['error' => $e->getMessage()]; } } + /** + * @throws \Exception + */ + public function aeRequestToExAppAsync( + ExApp $exApp, + string $route, + ?string $userId = null, + string $method = 'POST', + array $params = [], + array $options = [], + ?IRequest $request = null, + ): array|IPromise { + try { + $this->exAppUsersService->setupExAppUser($exApp->getAppid(), $userId); + $requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request); + return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']); + } catch (\Exception $e) { + $this->logger->warning(sprintf('Error while inserting ExApp %s user. Error: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); + throw new \Exception(sprintf('Error while inserting ExApp user: %s', $e->getMessage())); + } + } + + /** + * @throws \Exception + */ public function requestToExAppAsync( ExApp $exApp, string $route, @@ -127,35 +143,32 @@ public function requestToExAppAsync( array $params = [], array $options = [], ?IRequest $request = null, - ): void { + ): array|IPromise { $requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request); - $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']); + return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']); } + /** + * @throws \Exception if bad HTTP method + */ private function requestToExAppInternalAsync( ExApp $exApp, string $method, string $uri, #[\SensitiveParameter] array $options, - ): void { - switch ($method) { - case 'POST': - $promise = $this->client->postAsync($uri, $options); - break; - case 'PUT': - $promise = $this->client->putAsync($uri, $options); - break; - case 'DELETE': - $promise = $this->client->deleteAsync($uri, $options); - break; - default: - $this->logger->error('Bad HTTP method: requestToExAppAsync accepts only `POST`, `PUT` and `DELETE`'); - return; - } - $promise->then(function (IResponse $response) use ($exApp) { - }, function (\Exception $exception) use ($exApp) { + ): IPromise { + $promise = match ($method) { + 'GET' => $this->client->getAsync($uri, $options), + 'POST' => $this->client->postAsync($uri, $options), + 'PUT' => $this->client->putAsync($uri, $options), + 'DELETE' => $this->client->deleteAsync($uri, $options), + default => throw new \Exception('Bad HTTP method'), + }; + $promise->then(onRejected: function (\Exception $exception) use ($exApp) { + $this->logger->warning(sprintf('Error during requestToExAppAsync %s: %s', $exApp->getAppid(), $exception->getMessage()), ['exception' => $exception]); }); + return $promise; } private function prepareRequestToExApp( From 03a4f1f880455ab6e6b3cd83233fca3dcc3f29c4 Mon Sep 17 00:00:00 2001 From: Andrey Borysenko Date: Fri, 10 May 2024 15:44:27 +0300 Subject: [PATCH 2/4] update changelog Signed-off-by: Andrey Borysenko --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c10785fd..558557d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [2.6.0 - 2024-05-xx] +## [2.6.0 - 2024-05-10] ### Added - Added File Actions v2 version with redirect to the ExApp UI. #284 +- Added async requestToExApp public functions. #290 +- Added OCS API for synchronous requestToExApp functions. #290 ### Changed - Reworked scopes for database/cache requests optimization, drop old ex_app_scopes table. #285 +- Corrected "Download ExApp logs" button availability in "Test deploy". #289 + +### Fixed + +- Fixed incorrect init_timeout setting key in the UI. #288 ## [2.5.1 - 2024-05-02] From d5a4991106b8ac7fd247e2971893b131d7c41fe8 Mon Sep 17 00:00:00 2001 From: Andrey Borysenko Date: Fri, 10 May 2024 15:55:11 +0300 Subject: [PATCH 3/4] correct response types, error handling Signed-off-by: Andrey Borysenko --- lib/PublicFunctions.php | 4 ++-- lib/Service/AppAPIService.php | 13 ++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/PublicFunctions.php b/lib/PublicFunctions.php index 22a17b2d..2b336fb8 100644 --- a/lib/PublicFunctions.php +++ b/lib/PublicFunctions.php @@ -69,7 +69,7 @@ public function asyncExAppRequest( array $params = [], array $options = [], ?IRequest $request = null, - ): array|IPromise { + ): IPromise { $exApp = $this->exAppService->getExApp($appId); if ($exApp === null) { throw new \Exception(sprintf('ExApp `%s` not found', $appId)); @@ -90,7 +90,7 @@ public function asyncExAppRequestWithUserInit( array $params = [], array $options = [], ?IRequest $request = null, - ): array|IPromise { + ): IPromise { $exApp = $this->exAppService->getExApp($appId); if ($exApp === null) { throw new \Exception(sprintf('ExApp `%s` not found', $appId)); diff --git a/lib/Service/AppAPIService.php b/lib/Service/AppAPIService.php index 97548cbd..52cdf700 100644 --- a/lib/Service/AppAPIService.php +++ b/lib/Service/AppAPIService.php @@ -121,15 +121,10 @@ public function aeRequestToExAppAsync( array $params = [], array $options = [], ?IRequest $request = null, - ): array|IPromise { - try { - $this->exAppUsersService->setupExAppUser($exApp->getAppid(), $userId); - $requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request); - return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']); - } catch (\Exception $e) { - $this->logger->warning(sprintf('Error while inserting ExApp %s user. Error: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); - throw new \Exception(sprintf('Error while inserting ExApp user: %s', $e->getMessage())); - } + ): IPromise { + $this->exAppUsersService->setupExAppUser($exApp->getAppid(), $userId); + $requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request); + return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']); } /** From 4de24b7299d3ac0c4515ce55e6b0bb93af2dbb48 Mon Sep 17 00:00:00 2001 From: Andrey Borysenko Date: Fri, 10 May 2024 15:59:22 +0300 Subject: [PATCH 4/4] revert back error log for aeRequestToExApp Signed-off-by: Andrey Borysenko --- lib/Service/AppAPIService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Service/AppAPIService.php b/lib/Service/AppAPIService.php index 52cdf700..5e8f7f08 100644 --- a/lib/Service/AppAPIService.php +++ b/lib/Service/AppAPIService.php @@ -67,7 +67,7 @@ public function aeRequestToExApp( try { $this->exAppUsersService->setupExAppUser($exApp->getAppid(), $userId); } catch (\Exception $e) { - $this->logger->warning(sprintf('Error while inserting ExApp %s user. Error: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); + $this->logger->error(sprintf('Error while inserting ExApp %s user. Error: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]); return ['error' => 'Error while inserting ExApp user: ' . $e->getMessage()]; } return $this->requestToExApp($exApp, $route, $userId, $method, $params, $options, $request); @@ -138,7 +138,7 @@ public function requestToExAppAsync( array $params = [], array $options = [], ?IRequest $request = null, - ): array|IPromise { + ): IPromise { $requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request); return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']); }