Skip to content

Commit de235e2

Browse files
authored
WIP: add async requestToExApp, OCS API endpoints (#290)
This PR introduces async requestToExApp Public functions + two OCS endpoints for requestToExApp and exAppRequestWithUserInit. Some logging level adjustments. --------- Signed-off-by: Andrey Borysenko <[email protected]>
1 parent 37be01e commit de235e2

File tree

6 files changed

+196
-40
lines changed

6 files changed

+196
-40
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8-
## [2.6.0 - 2024-05-xx]
8+
## [2.6.0 - 2024-05-10]
99

1010
### Added
1111

1212
- Added File Actions v2 version with redirect to the ExApp UI. #284
13+
- Added async requestToExApp public functions. #290
14+
- Added OCS API for synchronous requestToExApp functions. #290
1315

1416
### Changed
1517

1618
- Reworked scopes for database/cache requests optimization, drop old ex_app_scopes table. #285
19+
- Corrected "Download ExApp logs" button availability in "Test deploy". #289
20+
21+
### Fixed
22+
23+
- Fixed incorrect init_timeout setting key in the UI. #288
1724

1825
## [2.5.1 - 2024-05-02]
1926

appinfo/routes.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
['name' => 'OCSExApp#getExAppsList', 'url' => '/api/v1/ex-app/{list}', 'verb' => 'GET'],
6565
['name' => 'OCSExApp#getExApp', 'url' => '/api/v1/ex-app/info/{appId}', 'verb' => 'GET'],
6666

67+
// Requests to ExApps
68+
['name' => 'OCSExApp#requestToExApp', 'url' => '/api/v1/ex-app/request/{appId}/', 'verb' => 'POST'],
69+
['name' => 'OCSExApp#aeRequestToExApp', 'url' => '/api/v1/ex-app/request/{appId}/{$userId}', 'verb' => 'POST'],
70+
6771
// ExApps actions
6872
['name' => 'OCSExApp#setExAppEnabled', 'url' => '/api/v1/ex-app/{appId}/enabled', 'verb' => 'PUT'],
6973

docs/tech_details/api/exapp.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,46 @@ The response data is a JSON array of ExApp objects with the following attributes
3232
"last_check_time": "timestamp of last successful Nextcloud->ExApp connection check",
3333
"system": "true/false flag indicating system ExApp",
3434
}
35+
36+
37+
Make Requests to ExApps
38+
^^^^^^^^^^^^^^^^^^^^^^^
39+
40+
There are two endpoints for making requests to ExApps:
41+
42+
1. Synchronous request: ``POST /apps/app_api/api/v1/ex-app/request/{appid}``
43+
2. Synchronous request with ExApp user setup: ``POST /apps/app_api/api/v1/ex-app/request/{appid}/{userId}``
44+
45+
Request data
46+
************
47+
48+
The request data params are the same as in ``lib/PublicFunction.php``:
49+
50+
.. code-block:: json
51+
52+
{
53+
"route": "relative route to ExApp API endpoint",
54+
"method": "GET/POST/PUT/DELETE",
55+
"params": {},
56+
"options": {},
57+
}
58+
59+
.. note::
60+
61+
``userId`` and ``appId`` is taken from url params
62+
63+
64+
Response data
65+
*************
66+
67+
Successful request to ExApp OCS data response structure is the following:
68+
69+
.. code-block:: json
70+
71+
{
72+
"status_code": "HTTP status code",
73+
"body": "response data from ExApp",
74+
"headers": "response headers from ExApp",
75+
}
76+
77+
If there is an error, the response object will have only an ``error`` attribute with the error message.

lib/Controller/OCSExAppController.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
use OCP\IRequest;
1717

1818
class OCSExAppController extends OCSController {
19+
protected $request;
1920

2021
public function __construct(
2122
IRequest $request,
2223
private readonly AppAPIService $service,
2324
private readonly ExAppService $exAppService,
2425
) {
2526
parent::__construct(Application::APP_ID, $request);
27+
28+
$this->request = $request;
2629
}
2730

2831
#[NoCSRFRequired]
@@ -70,4 +73,52 @@ public function setExAppEnabled(string $appId, int $enabled): DataResponse {
7073

7174
return new DataResponse();
7275
}
76+
77+
#[NoCSRFRequired]
78+
public function requestToExApp(
79+
string $appId,
80+
string $route,
81+
?string $userId = null,
82+
string $method = 'POST',
83+
array $params = [],
84+
array $options = [],
85+
): DataResponse {
86+
$exApp = $this->exAppService->getExApp($appId);
87+
if ($exApp === null) {
88+
return new DataResponse(['error' => sprintf('ExApp `%s` not found', $appId)]);
89+
}
90+
$response = $this->service->requestToExApp($exApp, $route, $userId, $method, $params, $options, $this->request);
91+
if (is_array($response) && isset($response['error'])) {
92+
return new DataResponse($response, Http::STATUS_BAD_REQUEST);
93+
}
94+
return new DataResponse([
95+
'status_code' => $response->getStatusCode(),
96+
'headers' => $response->getHeaders(),
97+
'body' => $response->getBody(),
98+
]);
99+
}
100+
101+
#[NoCSRFRequired]
102+
public function aeRequestToExApp(
103+
string $appId,
104+
string $route,
105+
?string $userId = null,
106+
string $method = 'POST',
107+
array $params = [],
108+
array $options = [],
109+
): DataResponse {
110+
$exApp = $this->exAppService->getExApp($appId);
111+
if ($exApp === null) {
112+
return new DataResponse(['error' => sprintf('ExApp `%s` not found', $appId)]);
113+
}
114+
$response = $this->service->aeRequestToExApp($exApp, $route, $userId, $method, $params, $options, $this->request);
115+
if (is_array($response) && isset($response['error'])) {
116+
return new DataResponse($response, Http::STATUS_BAD_REQUEST);
117+
}
118+
return new DataResponse([
119+
'status_code' => $response->getStatusCode(),
120+
'headers' => $response->getHeaders(),
121+
'body' => $response->getBody(),
122+
]);
123+
}
73124
}

lib/PublicFunctions.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use OCA\AppAPI\Service\AppAPIService;
88
use OCA\AppAPI\Service\ExAppService;
9+
use OCP\Http\Client\IPromise;
910
use OCP\Http\Client\IResponse;
1011
use OCP\IRequest;
1112

@@ -28,7 +29,7 @@ public function exAppRequest(
2829
array $params = [],
2930
array $options = [],
3031
?IRequest $request = null,
31-
): array|IResponse {
32+
): array|IResponse {
3233
$exApp = $this->exAppService->getExApp($appId);
3334
if ($exApp === null) {
3435
return ['error' => sprintf('ExApp `%s` not found', $appId)];
@@ -47,11 +48,53 @@ public function exAppRequestWithUserInit(
4748
array $params = [],
4849
array $options = [],
4950
?IRequest $request = null,
50-
): array|IResponse {
51+
): array|IResponse {
5152
$exApp = $this->exAppService->getExApp($appId);
5253
if ($exApp === null) {
5354
return ['error' => sprintf('ExApp `%s` not found', $appId)];
5455
}
5556
return $this->service->aeRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request);
5657
}
58+
59+
/**
60+
* Async request to ExApp with AppAPI auth headers
61+
*
62+
* @throws \Exception if ExApp not found
63+
*/
64+
public function asyncExAppRequest(
65+
string $appId,
66+
string $route,
67+
?string $userId = null,
68+
string $method = 'POST',
69+
array $params = [],
70+
array $options = [],
71+
?IRequest $request = null,
72+
): IPromise {
73+
$exApp = $this->exAppService->getExApp($appId);
74+
if ($exApp === null) {
75+
throw new \Exception(sprintf('ExApp `%s` not found', $appId));
76+
}
77+
return $this->service->requestToExAppAsync($exApp, $route, $userId, $method, $params, $options, $request);
78+
}
79+
80+
/**
81+
* Async request to ExApp with AppAPI auth headers and ExApp user initialization
82+
*
83+
* @throws \Exception if ExApp not found or failed to setup ExApp user
84+
*/
85+
public function asyncExAppRequestWithUserInit(
86+
string $appId,
87+
string $route,
88+
?string $userId = null,
89+
string $method = 'POST',
90+
array $params = [],
91+
array $options = [],
92+
?IRequest $request = null,
93+
): IPromise {
94+
$exApp = $this->exAppService->getExApp($appId);
95+
if ($exApp === null) {
96+
throw new \Exception(sprintf('ExApp `%s` not found', $appId));
97+
}
98+
return $this->service->aeRequestToExAppAsync($exApp, $route, $userId, $method, $params, $options, $request);
99+
}
57100
}

lib/Service/AppAPIService.php

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use OCP\DB\Exception;
1515
use OCP\Http\Client\IClient;
1616
use OCP\Http\Client\IClientService;
17+
use OCP\Http\Client\IPromise;
1718
use OCP\Http\Client\IResponse;
1819
use OCP\IConfig;
1920
use OCP\IRequest;
@@ -96,29 +97,39 @@ private function requestToExAppInternal(
9697
array $options,
9798
): array|IResponse {
9899
try {
99-
switch ($method) {
100-
case 'GET':
101-
$response = $this->client->get($uri, $options);
102-
break;
103-
case 'POST':
104-
$response = $this->client->post($uri, $options);
105-
break;
106-
case 'PUT':
107-
$response = $this->client->put($uri, $options);
108-
break;
109-
case 'DELETE':
110-
$response = $this->client->delete($uri, $options);
111-
break;
112-
default:
113-
return ['error' => 'Bad HTTP method'];
114-
}
115-
return $response;
100+
return match ($method) {
101+
'GET' => $this->client->get($uri, $options),
102+
'POST' => $this->client->post($uri, $options),
103+
'PUT' => $this->client->put($uri, $options),
104+
'DELETE' => $this->client->delete($uri, $options),
105+
default => ['error' => 'Bad HTTP method'],
106+
};
116107
} catch (\Exception $e) {
117-
$this->logger->error(sprintf('Error during request to ExApp %s: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]);
108+
$this->logger->warning(sprintf('Error during request to ExApp %s: %s', $exApp->getAppid(), $e->getMessage()), ['exception' => $e]);
118109
return ['error' => $e->getMessage()];
119110
}
120111
}
121112

113+
/**
114+
* @throws \Exception
115+
*/
116+
public function aeRequestToExAppAsync(
117+
ExApp $exApp,
118+
string $route,
119+
?string $userId = null,
120+
string $method = 'POST',
121+
array $params = [],
122+
array $options = [],
123+
?IRequest $request = null,
124+
): IPromise {
125+
$this->exAppUsersService->setupExAppUser($exApp->getAppid(), $userId);
126+
$requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request);
127+
return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']);
128+
}
129+
130+
/**
131+
* @throws \Exception
132+
*/
122133
public function requestToExAppAsync(
123134
ExApp $exApp,
124135
string $route,
@@ -127,35 +138,32 @@ public function requestToExAppAsync(
127138
array $params = [],
128139
array $options = [],
129140
?IRequest $request = null,
130-
): void {
141+
): IPromise {
131142
$requestData = $this->prepareRequestToExApp($exApp, $route, $userId, $method, $params, $options, $request);
132-
$this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']);
143+
return $this->requestToExAppInternalAsync($exApp, $method, $requestData['url'], $requestData['options']);
133144
}
134145

146+
/**
147+
* @throws \Exception if bad HTTP method
148+
*/
135149
private function requestToExAppInternalAsync(
136150
ExApp $exApp,
137151
string $method,
138152
string $uri,
139153
#[\SensitiveParameter]
140154
array $options,
141-
): void {
142-
switch ($method) {
143-
case 'POST':
144-
$promise = $this->client->postAsync($uri, $options);
145-
break;
146-
case 'PUT':
147-
$promise = $this->client->putAsync($uri, $options);
148-
break;
149-
case 'DELETE':
150-
$promise = $this->client->deleteAsync($uri, $options);
151-
break;
152-
default:
153-
$this->logger->error('Bad HTTP method: requestToExAppAsync accepts only `POST`, `PUT` and `DELETE`');
154-
return;
155-
}
156-
$promise->then(function (IResponse $response) use ($exApp) {
157-
}, function (\Exception $exception) use ($exApp) {
155+
): IPromise {
156+
$promise = match ($method) {
157+
'GET' => $this->client->getAsync($uri, $options),
158+
'POST' => $this->client->postAsync($uri, $options),
159+
'PUT' => $this->client->putAsync($uri, $options),
160+
'DELETE' => $this->client->deleteAsync($uri, $options),
161+
default => throw new \Exception('Bad HTTP method'),
162+
};
163+
$promise->then(onRejected: function (\Exception $exception) use ($exApp) {
164+
$this->logger->warning(sprintf('Error during requestToExAppAsync %s: %s', $exApp->getAppid(), $exception->getMessage()), ['exception' => $exception]);
158165
});
166+
return $promise;
159167
}
160168

161169
private function prepareRequestToExApp(

0 commit comments

Comments
 (0)