Skip to content

Commit

Permalink
Declarative Settings API (#149)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Borysenko <[email protected]>
Signed-off-by: Alexander Piskun <[email protected]>
Co-authored-by: Alexander Piskun <[email protected]>
Co-authored-by: Alexander Piskun <[email protected]>
  • Loading branch information
3 people authored Feb 11, 2024
1 parent 5dc59a6 commit 5e492fb
Show file tree
Hide file tree
Showing 19 changed files with 852 additions and 3 deletions.
6 changes: 5 additions & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ to join us in shaping a more versatile, stable, and secure app landscape.
*Your insights, suggestions, and contributions are invaluable to us.*
]]></description>
<version>2.0.4</version>
<version>2.1.0</version>
<licence>agpl</licence>
<author mail="[email protected]" homepage="https://github.com/andrey18106">Andrey Borysenko</author>
<author mail="[email protected]" homepage="https://github.com/bigcat88">Alexander Piskun</author>
Expand Down Expand Up @@ -96,5 +96,9 @@ to join us in shaping a more versatile, stable, and secure app landscape.
<settings>
<admin>OCA\AppAPI\Settings\Admin</admin>
<admin-section>OCA\AppAPI\Settings\AdminSection</admin-section>
<admin-section>OCA\AppAPI\Settings\DeclarativeSettings\AiIntegrationTeam</admin-section>
<admin-section>OCA\AppAPI\Settings\DeclarativeSettings\DeclarativeSettings</admin-section>
<personal-section>OCA\AppAPI\Settings\DeclarativeSettings\AiIntegrationTeam</personal-section>
<personal-section>OCA\AppAPI\Settings\DeclarativeSettings\DeclarativeSettings</personal-section>
</settings>
</info>
5 changes: 5 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@
['name' => 'OCSUi#deleteExAppStyle', 'url' => '/api/v1/ui/style', 'verb' => 'DELETE'],
['name' => 'OCSUi#getExAppStyle', 'url' => '/api/v1/ui/style', 'verb' => 'GET'],

// Declarative settings
['name' => 'OCSSettings#registerForm', 'url' => '/api/v1/ui/settings', 'verb' => 'POST'],
['name' => 'OCSSettings#unregisterForm', 'url' => '/api/v1/ui/settings', 'verb' => 'DELETE'],
['name' => 'OCSSettings#getForm', 'url' => '/api/v1/ui/settings', 'verb' => 'GET'],

// Speech-To-Text
['name' => 'speechToText#registerProvider', 'url' => '/api/v1/ai_provider/speech_to_text', 'verb' => 'POST'],
['name' => 'speechToText#unregisterProvider', 'url' => '/api/v1/ai_provider/speech_to_text', 'verb' => 'DELETE'],
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 @@ -16,6 +16,7 @@ AppAPI Nextcloud APIs
utils
fileactionsmenu
topmenu
settings
notifications
talkbots
speechtotext
Expand Down
209 changes: 209 additions & 0 deletions docs/tech_details/api/settings.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
====================
Declarative Settings
====================

Starting from Nextcloud **29**, AppAPI provides the ability to display ex-application settings.
When admin or user changes some ex-app settings
they will be stored in the database and can be received using :doc:`preferences` or :doc:`appconfig` API.

.. note::

Settings rendered only for enabled ExApps.

.. warning::

``section_id`` from **scheme** should be already registered by any PHP application.

**AppAPI** provides two sections for that: ``ai_integration_team`` and ``declarative_settings``, you can use them.

Register Settings
^^^^^^^^^^^^^^^^^

OCS endpoint: ``POST /apps/app_api/api/v1/ui/settings``

Params
******

Complete list of params (including optional):

.. code-block:: json
{
"formScheme": "settings scheme"
}
Unregister Menu Entry
^^^^^^^^^^^^^^^^^^^^^

OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/settings``

Params
******

.. code-block:: json
{
"formId": "formId from scheme"
}
Example of settings scheme in Python:

.. code-block:: python
{
"id": "settings_example",
"priority": 10,
"section_type": "admin",
"section_id": "ai_integration_team",
"title": "AppAPI declarative settings",
"description": "These fields are rendered dynamically from declarative schema",
"fields": [
{
"id": "field1",
"title": "Multi-selection",
"description": "Select some option setting",
"type": 'multi-select',
"options": ["foo", "bar", "baz"],
"placeholder": "Select some multiple options",
"default": ["foo", "bar"],
},
{
"id": "some_real_setting",
'title': 'Choose init status check background job interval',
'description': 'How often AppAPI should check for initialization status',
'type': 'radio',
'placeholder': 'Choose init status check background job interval',
'default': '40m',
'options': [
{
'name': 'Each 40 minutes',
'value': '40m',
},
{
'name': 'Each 60 minutes',
'value': '60m',
},
{
'name': 'Each 120 minutes',
'value': '120m',
},
{
'name': 'Each day',
'value': f"{60 * 24}m",
},
],
},
{
'id': 'test_ex_app_field_1',
'title': 'Default text field',
'description': 'Set some simple text setting',
'type': 'text',
'placeholder': 'Enter text setting',
'default': 'foo',
},
{
'id': 'test_ex_app_field_1_1',
'title': 'Email field',
'description': 'Set email config',
'type': 'email',
'placeholder': 'Enter email',
'default': '',
},
{
'id': 'test_ex_app_field_1_2',
'title': 'Tel field',
'description': 'Set tel config',
'type': 'tel',
'placeholder': 'Enter your tel',
'default': '',
},
{
'id': 'test_ex_app_field_1_3',
'title': 'Url (website) field',
'description': 'Set url config',
'type': url',
'placeholder': 'Enter url',
'default': '',
},
{
'id': 'test_ex_app_field_1_4',
'title': 'Number field',
'description': 'Set number config',
'type': 'number',
'placeholder': 'Enter number value',
'default': 0,
},
{
'id': 'test_ex_app_field_2',
'title': 'Password',
'description': 'Set some secure value setting',
'type': password',
'placeholder': 'Set secure value',
'default': '',
},
{
'id': 'test_ex_app_field_3',
'title': 'Selection',
'description': 'Select some option setting',
'type': 'select',
'options': ['foo', 'bar', 'baz'],
'placeholder': 'Select some option setting',
'default': 'foo',
},
{
'id': 'test_ex_app_field_4',
'title': 'Toggle something',
'description': 'Select checkbox option setting',
'type': 'checkbox',
'label': 'Verify something if enabled',
'default': False,
},
{
'id': 'test_ex_app_field_5',
'title': 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}',
'description': 'Select checkbox option setting',
'type': 'multi-checkbox',
'default': {'foo': True, 'bar': True},
'options': [
{
'name':'Foo',
'value': 'foo',
},
{
'name': 'Bar',
'value': 'bar',
},
{
'name': 'Baz',
'value': 'baz',
},
{
'name': 'Qux',
'value': 'qux',
},
],
},
{
'id': 'test_ex_app_field_6',
'title': 'Radio toggles, describing one setting like single select',
'description': 'Select radio option setting',
'type': 'radio',
'label': 'Select single toggle',
'default': 'foo',
'options': [
{
'name': 'First radio',
'value': 'foo'
},
{
'name': 'Second radio',
'value': 'bar'
},
{
'name': 'Second radio',
'value': 'baz'
},
],
},
]
}
2 changes: 1 addition & 1 deletion docs/tech_details/api/topmenu.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Optional params
Unregister Menu Entry
^^^^^^^^^^^^^^^^^^^^^

OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/files-actions-menu``
OCS endpoint: ``DELETE /apps/app_api/api/v1/ui/top-menu``

Params
******
Expand Down
11 changes: 10 additions & 1 deletion lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use OCA\AppAPI\Capabilities;
use OCA\AppAPI\DavPlugin;
use OCA\AppAPI\Listener\DeclarativeSettings\GetValueListener;
use OCA\AppAPI\Listener\DeclarativeSettings\RegisterDeclarativeSettingsListener;
use OCA\AppAPI\Listener\DeclarativeSettings\SetValueListener;
use OCA\AppAPI\Listener\LoadFilesPluginListener;
use OCA\AppAPI\Listener\SabrePluginAuthInitListener;
use OCA\AppAPI\Listener\UserDeletedListener;
Expand Down Expand Up @@ -34,6 +37,9 @@
use OCP\IUserSession;
use OCP\Profiler\IProfiler;
use OCP\SabrePluginEvent;
use OCP\Settings\GetDeclarativeSettingsValueEvent;
use OCP\Settings\RegisterDeclarativeSettingsFormEvent;
use OCP\Settings\SetDeclarativeSettingsValueEvent;
use OCP\User\Events\UserDeletedEvent;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
Expand Down Expand Up @@ -62,7 +68,10 @@ public function register(IRegistrationContext $context): void {
$context->registerNotifierService(ExAppNotifier::class);
$context->registerNotifierService(ExAppAdminNotifier::class);

// Dynamic anonymous providers registration
$context->registerEventListener(RegisterDeclarativeSettingsFormEvent::class, RegisterDeclarativeSettingsListener::class);
$context->registerEventListener(GetDeclarativeSettingsValueEvent::class, GetValueListener::class);
$context->registerEventListener(SetDeclarativeSettingsValueEvent::class, SetValueListener::class);

$container = $this->getContainer();
try {
/** @var SpeechToTextService $speechToTextService */
Expand Down
78 changes: 78 additions & 0 deletions lib/Controller/OCSSettingsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace OCA\AppAPI\Controller;

use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Attribute\AppAPIAuth;
use OCA\AppAPI\Service\UI\SettingsService;
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\IConfig;
use OCP\IRequest;

class OCSSettingsController extends OCSController {
protected $request;

public function __construct(
IRequest $request,
private readonly SettingsService $settingsService,
private readonly IConfig $config,
) {
parent::__construct(Application::APP_ID, $request);

$this->request = $request;
}

#[NoCSRFRequired]
#[PublicPage]
#[AppAPIAuth]
public function registerForm(array $formScheme): DataResponse {
$ncVersion = $this->config->getSystemValueString('version', '0.0.0');
if (version_compare($ncVersion, '29.0', '<')) {
return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED);
}
$settingsForm = $this->settingsService->registerForm(
$this->request->getHeader('EX-APP-ID'), $formScheme);
if ($settingsForm === null) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
return new DataResponse();
}

#[NoCSRFRequired]
#[PublicPage]
#[AppAPIAuth]
public function unregisterForm(string $formId): DataResponse {
$ncVersion = $this->config->getSystemValueString('version', '0.0.0');
if (version_compare($ncVersion, '29.0', '<')) {
return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED);
}
$unregistered = $this->settingsService->unregisterForm(
$this->request->getHeader('EX-APP-ID'), $formId);
if ($unregistered === null) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
return new DataResponse();
}

#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function getForm(string $formId): DataResponse {
$ncVersion = $this->config->getSystemValueString('version', '0.0.0');
if (version_compare($ncVersion, '29.0', '<')) {
return new DataResponse([], Http::STATUS_NOT_IMPLEMENTED);
}
$result = $this->settingsService->getForm(
$this->request->getHeader('EX-APP-ID'), $formId);
if (!$result) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
return new DataResponse($result->getScheme(), Http::STATUS_OK);
}
}
Loading

0 comments on commit 5e492fb

Please sign in to comment.