Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
4 changes: 4 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],

Expand Down
43 changes: 43 additions & 0 deletions docs/tech_details/api/exapp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
51 changes: 51 additions & 0 deletions lib/Controller/OCSExAppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
use OCP\IRequest;

class OCSExAppController extends OCSController {
protected $request;

public function __construct(
IRequest $request,
private readonly AppAPIService $service,
private readonly ExAppService $exAppService,
) {
parent::__construct(Application::APP_ID, $request);

$this->request = $request;
}

#[NoCSRFRequired]
Expand Down Expand Up @@ -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(),
]);
}
}
47 changes: 45 additions & 2 deletions lib/PublicFunctions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)];
Expand All @@ -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,
): 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,
): 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);
}
}
82 changes: 45 additions & 37 deletions lib/Service/AppAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -96,29 +97,39 @@ 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,
): 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']);
}

/**
* @throws \Exception
*/
public function requestToExAppAsync(
ExApp $exApp,
string $route,
Expand All @@ -127,35 +138,32 @@ public function requestToExAppAsync(
array $params = [],
array $options = [],
?IRequest $request = null,
): void {
): 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(
Expand Down