Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExApp occ commands API #272

Merged
merged 16 commits into from
Apr 19, 2024
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
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
Loading