Skip to content

Commit

Permalink
ExApp occ commands API (#272)
Browse files Browse the repository at this point in the history
This PR introduces API for registering OCC commands for ExApps.

Note: **Passing file contents as the input argument is not supported**

---------

Signed-off-by: Andrey Borysenko <[email protected]>
Co-authored-by: Alexander Piskun <[email protected]>
  • Loading branch information
andrey18106 and bigcat88 authored Apr 19, 2024
1 parent 4e1313d commit 052e3a0
Show file tree
Hide file tree
Showing 15 changed files with 736 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Different compute device configuration for Daemon (NVIDIA, AMD, CPU). #267
- Ability to add optional parameters when registering a daemon, for example *OVERRIDE_APP_HOST*. #269
- API for registering OCC commands. #272
- Correct support of the Docker `HEALTHCHECK` instruction. #273
- Support of pulling "custom" images for the selected compute device. #274

Expand Down
32 changes: 32 additions & 0 deletions appinfo/register_command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\Console\Application as SymfonyApplication;

use OCP\Server;
use OCP\IConfig;
use OCA\AppAPI\Service\ExAppOccService;
use OCA\AppAPI\Db\Console\ExAppOccCommand;

try {
$config = Server::get(IConfig::class);
$serverContainer = Server::get(ContainerInterface::class);
if ($config->getSystemValueBool('installed', false)) {
$exAppOccService = Server::get(ExAppOccService::class);
/**
* @var ExAppOccCommand $occCommand
* @var SymfonyApplication $application
*/
foreach ($exAppOccService->getOccCommands() as $occCommand) {
$application->add($exAppOccService->buildCommand(
$occCommand,
$serverContainer
));
}
}
} catch (NotFoundExceptionInterface|ContainerExceptionInterface $e) {
}
5 changes: 5 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
['name' => 'EventsListener#unregisterListener', 'url' => '/api/v1/events_listener', 'verb' => 'DELETE'],
['name' => 'EventsListener#getListener', 'url' => '/api/v1/events_listener', 'verb' => 'GET'],

// Commands
['name' => 'OccCommand#registerCommand', 'url' => '/api/v1/occ_command', 'verb' => 'POST'],
['name' => 'OccCommand#unregisterCommand', 'url' => '/api/v1/occ_command', 'verb' => 'DELETE'],
['name' => 'OccCommand#getCommand', 'url' => '/api/v1/occ_command', 'verb' => 'GET'],

// Talk bots
['name' => 'TalkBot#registerExAppTalkBot', 'url' => '/api/v1/talk_bot', 'verb' => 'POST'],
['name' => 'TalkBot#unregisterExAppTalkBot', 'url' => '/api/v1/talk_bot', 'verb' => 'DELETE'],
Expand Down
1 change: 1 addition & 0 deletions docs/tech_details/ApiScopes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Supported API Groups include:
* ``60`` TALK_BOT
* ``61`` AI_PROVIDERS
* ``62`` EVENTS_LISTENER
* ``63`` OCC_COMMAND
* ``110`` ACTIVITIES
* ``120`` NOTES
* ``200`` TEXT_PROCESSING
Expand Down
1 change: 1 addition & 0 deletions docs/tech_details/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ AppAPI Nextcloud APIs
settings
notifications
events_listener
occ_command
talkbots
speechtotext
textprocessing
Expand Down
108 changes: 108 additions & 0 deletions docs/tech_details/api/occ_command.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
.. _occ_command:

===========
OCC Command
===========

This API allows you to register the occ (CLI) commands.
The principal is similar to the regular Nextcloud OCC command for PHP apps, that are working in context of the Nextcloud instance,
but for ExApps it is a trigger via Nextcloud OCC interface to perform some action on the External App side.


.. note::

Passing files directly as an input argument to the occ command is not supported.

Register
^^^^^^^^

OCS endpoint: ``POST /apps/app_api/api/v1/occ_command``

Params
******

.. code-block:: json
{
"name": "appid:unique:command:name",
"description": "Description of the command",
"hidden": "true/false",
"arguments": [
{
"name": "argument_name",
"mode": "required/optional/array",
"description": "Description of the argument",
"default": "default_value"
}
],
"options": [
{
"name": "option_name",
"shortcut": "s",
"mode": "required/optional/none/array/negatable",
"description": "Description of the option",
"default": "default_value"
}
],
"usages": [
"occ appid:unique:command:name argument_name --option_name",
"occ appid:unique:command:name argument_name -s"
],
"execute_handler": "handler_route"
}
For more details on the command arguments and options modes,
see the original docs for the Symfony console input parameters, which are actually being built from the provided data:
`https://symfony.com/doc/current/console/input.html#using-command-arguments <https://symfony.com/doc/current/console/input.html#using-command-arguments>`_


Example
*******

Lets assume we have a command `ping` that takes an argument `test_arg` and has an option `test-option`:

.. code-block:: json
{
"name": "my_app_id:ping",
"description": "Test ping command",
"hidden": "false",
"arguments": [
{
"name": "test_arg",
"mode": "required",
"description": "Test argument",
"default": 123
}
],
"options": [
{
"name": "test-option",
"shortcut": "t",
"mode": "none",
"description": "Test option",
}
],
"usages": [
"occ my_app_id:ping 12345",
"occ my_app_id:ping 12345 --test-option",
"occ my_app_id:ping 12345 -t"
],
"execute_handler": "handler_route"
}
Unregister
^^^^^^^^^^

OCS endpoint: ``DELETE /apps/app_api/api/v1/occ_command``

Params
******

To unregister OCC Command, you just need to provide a command `name`:

.. code-block:: json
{
"name": "occ_command_name"
}
72 changes: 72 additions & 0 deletions lib/Controller/OccCommandController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace OCA\AppAPI\Controller;

use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Attribute\AppAPIAuth;
use OCA\AppAPI\Service\ExAppOccService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;

class OccCommandController extends OCSController {
protected $request;

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

$this->request = $request;
}

#[NoCSRFRequired]
#[PublicPage]
#[AppAPIAuth]
public function registerCommand(
string $name,
string $description,
string $execute_handler,
bool $hidden = false,
array $arguments = [],
array $options = [],
array $usages = [],
): DataResponse {
$command = $this->service->registerCommand(
$this->request->getHeader('EX-APP-ID'), $name,
$description, $hidden, $arguments, $options, $usages, $execute_handler
);
if ($command === null) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
return new DataResponse();
}

#[NoCSRFRequired]
#[PublicPage]
#[AppAPIAuth]
public function unregisterCommand(string $name): DataResponse {
$unregistered = $this->service->unregisterCommand($this->request->getHeader('EX-APP-ID'), $name);
if (!$unregistered) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
return new DataResponse();
}

#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function getCommand(string $name): DataResponse {
$result = $this->service->getOccCommand($this->request->getHeader('EX-APP-ID'), $name);
if (!$result) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
return new DataResponse($result, Http::STATUS_OK);
}
}
97 changes: 97 additions & 0 deletions lib/Db/Console/ExAppOccCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

declare(strict_types=1);

namespace OCA\AppAPI\Db\Console;

use JsonSerializable;
use OCP\AppFramework\Db\Entity;

/**
* Class ExAppOccCommand
*
* @package OCA\AppAPI\Db\Console
*
* @method string getAppid()
* @method string getName()
* @method string getDescription()
* @method bool getHidden()
* @method array getArguments()
* @method array getOptions()
* @method array getUsages()
* @method string getExecuteHandler()
* @method void setAppid(string $appid)
* @method void setName(string $name)
* @method void setDescription(string $description)
* @method void setHidden(bool $hidden)
* @method void setArguments(array $arguments)
* @method void setOptions(array $options)
* @method void setUsages(array $usages)
* @method void setExecuteHandler(string $executeHandler)
*/
class ExAppOccCommand extends Entity implements JsonSerializable {
protected $appid;
protected $name;
protected $description;
protected $hidden;
protected $arguments;
protected $options;
protected $usages;
protected $executeHandler;

/**
* @param array $params
*/
public function __construct(array $params = []) {
$this->addType('appid', 'string');
$this->addType('name', 'string');
$this->addType('description', 'string');
$this->addType('hidden', 'bool');
$this->addType('arguments', 'json');
$this->addType('options', 'json');
$this->addType('usages', 'json');
$this->addType('executeHandler', 'string');

if (isset($params['id'])) {
$this->setId($params['id']);
}
if (isset($params['appid'])) {
$this->setAppid($params['appid']);
}
if (isset($params['name'])) {
$this->setName($params['name']);
}
if (isset($params['description'])) {
$this->setDescription($params['description']);
}
if (isset($params['hidden'])) {
$this->setHidden($params['hidden']);
}
if (isset($params['arguments'])) {
$this->setArguments($params['arguments']);
}
if (isset($params['options'])) {
$this->setOptions($params['options']);
}
if (isset($params['usages'])) {
$this->setUsages($params['usages']);
}
if (isset($params['execute_handler'])) {
$this->setExecuteHandler($params['execute_handler']);
}
}

public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'appid' => $this->getAppid(),
'name' => $this->getName(),
'description' => $this->getDescription(),
'hidden' => $this->getHidden(),
'arguments' => $this->getArguments(),
'options' => $this->getOptions(),
'usages' => $this->getUsages(),
'execute_handler' => $this->getExecuteHandler(),
];
}
}
Loading

0 comments on commit 052e3a0

Please sign in to comment.