Skip to content

Commit

Permalink
Notifications adjustments. DeployActions. Docs updates (#25)
Browse files Browse the repository at this point in the history
- [x] DeployActions interface
- [x] Notifications adjustments (receive dynamic parameters to build
known subjects)
- [ ] Write docs about FileActionsMenu, Notifications

---------

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
Co-authored-by: Alexander Piskun <bigcat88@icloud.com>
  • Loading branch information
andrey18106 and bigcat88 authored Aug 2, 2023
1 parent 9a24411 commit 08b72fe
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
APP_PORT: 9009
APP_VERSION: "1.0.0"
APP_SECRET: "tC6vkwPhcppjMykD1r0n9NlI95uJMBYjs5blpIcA1PAdoPDmc5qoAjaBAkyocZ6E"
SKIP_NC_WO_AE: 1
SKIP_NC_CLIENT_TESTS: 1

services:
postgres:
Expand Down
8 changes: 8 additions & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
===================
AppEcosystemV2 APIs
===================


.. toctree::

notifications
61 changes: 61 additions & 0 deletions docs/api/notifications.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
=============
Notifications
=============

AppEcosystemV2 allows ExApps to send limited notifications to users.
ExApp can send simple notification using available `rich object strings <https://github.com/nextcloud/server/blob/master/lib/public/RichObjectStrings/Definitions.php#L42>`_.
More info about rich objects string can be found `here <https://github.com/nextcloud/server/issues/1706>`_.

Send notification (OCS)
^^^^^^^^^^^^^^^^^^^^^^^

OCS endpoint: ``POST /apps/app_ecosystem_v2/api/v1/notification``

Request payload
***************

.. code-block:: json
{
"params": {
"object": "app_ecosystem_v2",
"object_id": "app_ecosystem_v2_id",
"subject_type": "app_ecosystem_v2_ex_app",
"subject_params": {
"rich_subject": "Image {file} successfully upscaled!",
"rich_subject_params": {
"file": {
"type": "file",
"id": 123,
"name": "upscaled_image_name",
"path": "path/to/upscaled_image_name"
}
},
"rich_message": "{user} checkout results!",
"rich_message_params": {
"user": {
"type": "user",
"id": "admin",
"name": "admin"
}
},
"link": "http(s)://nextcloud.local/index.php/apps/files/?fileid=123"
}
}
}
Params
^^^^^^

Required payload params:

* ``object`` - ``[required]`` should be set to default value, not used yet
* ``object_id`` - ``[required]`` should be set to default value, not used yet
* ``subject_type`` - ``[required]`` subject type should be set to default value, not used yet
* ``subject_params`` - ``[required]``
* ``rich_subject`` - ``[optional]`` rich subject (title) string
* ``rich_subject_params`` - ``[optional]`` rich subject (title) params to replace rich objects in string
* ``rich_message`` - ``[optional]`` rich message string
* ``rich_message_params`` - ``[optional`` rich message params to replace objects in string
* ``link`` - absolute url to set for notification link
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Welcome to the amazing docs that we will write for AppEcosystemV2!
development/index.rst
deploy/index.rst
authentication
api/index.rst
2 changes: 1 addition & 1 deletion lib/Command/ExApp/Deploy.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __construct(

$this->service = $service;
$this->daemonConfigService = $daemonConfigService;
$this->dockerActions = $dockerActions; // TODO: Change to dynamic DeployActions resolving
$this->dockerActions = $dockerActions;
$this->appManager = $appManager;
$this->random = $random;
$this->urlGenerator = $urlGenerator;
Expand Down
33 changes: 19 additions & 14 deletions lib/Controller/NotificationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,59 @@
use OCA\AppEcosystemV2\AppInfo\Application;
use OCA\AppEcosystemV2\Attribute\AppEcosystemAuth;
use OCA\AppEcosystemV2\Notifications\ExNotificationsManager;
use OCA\AppEcosystemV2\Service\AppEcosystemV2Service;

use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\Notification\INotification;

class NotificationsController extends OCSController {
private AppEcosystemV2Service $service;
private ExNotificationsManager $exNotificationsManager;
protected $request;

public function __construct(
IRequest $request,
AppEcosystemV2Service $service,
ExNotificationsManager $exNotificationsManager,
) {
parent::__construct(Application::APP_ID, $request);

$this->request = $request;
$this->service = $service;
$this->exNotificationsManager = $exNotificationsManager;
}

/**
* @NoCSRFRequired
* @PublicPage
*
* @param array $params [object, object_id, subject, subject_params]
* @param array $params
*
* @throws OCSNotFoundException
* @return Response
*/
#[AppEcosystemAuth]
#[PublicPage]
#[NoCSRFRequired]
public function sendNotification(array $params = []): Response {
public function sendNotification(array $params): Response {
$appId = $this->request->getHeader('EX-APP-ID');
$userId = $this->request->getHeader('NC-USER-ID');
$exApp = $this->service->getExApp($appId);
if ($exApp === null) {
throw new OCSNotFoundException('ExApp not found');
}
$notification = $this->exNotificationsManager->sendNotification($exApp, $userId, $params);
return new DataResponse($notification, Http::STATUS_OK);
$notification = $this->exNotificationsManager->sendNotification($appId, $userId, $params);
return new DataResponse($this->notificationToArray($notification), Http::STATUS_OK);
}

private function notificationToArray(INotification $notification): array {
return [
'app' => $notification->getApp(),
'user' => $notification->getUser(),
'datetime' => $notification->getDateTime()->format('c'),
'object_type' => $notification->getObjectType(),
'object_id' => $notification->getObjectId(),
'subject' => $notification->getParsedSubject(),
'message' => $notification->getParsedMessage(),
'link' => $notification->getLink(),
'icon' => $notification->getIcon(),
];
}
}
3 changes: 1 addition & 2 deletions lib/DeployActions/DockerActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
use GuzzleHttp\Exception\GuzzleException;

use OCA\AppEcosystemV2\Db\DaemonConfig;
use OCA\AppEcosystemV2\Deploy\AbstractDeployActions;

use OCP\ICertificateManager;
use OCP\IConfig;
use Psr\Log\LoggerInterface;

class DockerActions extends AbstractDeployActions {
class DockerActions implements IDeployActions {
public const DOCKER_API_VERSION = 'v1.41';
public const AE_REQUIRED_ENVS = [
'AE_VERSION',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

declare(strict_types=1);

namespace OCA\AppEcosystemV2\Deploy;
namespace OCA\AppEcosystemV2\DeployActions;

use OCA\AppEcosystemV2\Db\DaemonConfig;

/**
* Base class for AppEcosystemV2 ExApp deploy actions
* Base interface for AppEcosystemV2 ExApp deploy actions
*/
abstract class AbstractDeployActions {
interface IDeployActions {
/**
* Deploy type (action) id name
*
* @return string
*/
abstract public function getAcceptsDeployId(): string;
public function getAcceptsDeployId(): string;

/**
* Deploy ExApp to the target daemon
Expand All @@ -25,7 +25,7 @@ abstract public function getAcceptsDeployId(): string;
*
* @return mixed
*/
abstract public function deployExApp(DaemonConfig $daemonConfig, array $params = []): mixed;
public function deployExApp(DaemonConfig $daemonConfig, array $params = []): mixed;

/**
* Load ExApp information from the target daemon.
Expand All @@ -36,7 +36,7 @@ abstract public function deployExApp(DaemonConfig $daemonConfig, array $params =
*
* @return array required data for ExApp registration
*/
abstract public function loadExAppInfo(string $appId, DaemonConfig $daemonConfig, array $params = []): array;
public function loadExAppInfo(string $appId, DaemonConfig $daemonConfig, array $params = []): array;

/**
* Resolve ExApp host depending on daemon configuration.
Expand All @@ -48,5 +48,5 @@ abstract public function loadExAppInfo(string $appId, DaemonConfig $daemonConfig
*
* @return string
*/
abstract public function resolveDeployExAppHost(string $appId, DaemonConfig $daemonConfig, array $params = []): string;
public function resolveDeployExAppHost(string $appId, DaemonConfig $daemonConfig, array $params = []): string;
}
3 changes: 1 addition & 2 deletions lib/DeployActions/ManualActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
namespace OCA\AppEcosystemV2\DeployActions;

use OCA\AppEcosystemV2\Db\DaemonConfig;
use OCA\AppEcosystemV2\Deploy\AbstractDeployActions;

/**
* Manual deploy actions for development.
*/
class ManualActions extends AbstractDeployActions {
class ManualActions implements IDeployActions {
public function getAcceptsDeployId(): string {
return 'manual-install';
}
Expand Down
52 changes: 18 additions & 34 deletions lib/Notifications/ExAppNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,24 @@

use OCA\AppEcosystemV2\AppInfo\Application;
use OCA\AppEcosystemV2\Service\AppEcosystemV2Service;
use OCA\AppEcosystemV2\Service\ExFilesActionsMenuService;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;

class ExAppNotifier implements INotifier {
public const FILE_ACTION_MENU_ALERT = 'file_action_menu_alert';
private IFactory $factory;
private IURLGenerator $url;
private AppEcosystemV2Service $service;
private ExFilesActionsMenuService $exFilesActionsMenuService;

public function __construct(
IFactory $factory,
IURLGenerator $urlGenerator,
AppEcosystemV2Service $service,
ExFilesActionsMenuService $exFilesActionsMenuService,
) {
$this->factory = $factory;
$this->url = $urlGenerator;
$this->service = $service;
$this->exFilesActionsMenuService = $exFilesActionsMenuService;
}

public function getID(): string {
Expand All @@ -40,39 +35,28 @@ public function getName(): string {
}

public function prepare(INotification $notification, string $languageCode): INotification {
$exApps = $this->service->getExAppsList();
if (!in_array($notification->getApp(), $exApps)) {
$exApp = $this->service->getExApp($notification->getApp());
if ($exApp === null) {
throw new \InvalidArgumentException();
}
// Only enabled ExApps can render notifications
if (!$exApp->getEnabled()) {
throw new \InvalidArgumentException('ExApp is disabled');
}

$l = $this->factory->get(Application::APP_ID, $languageCode);

switch($notification->getSubject()) {
case self::FILE_ACTION_MENU_ALERT:
$subjectParameters = $notification->getSubjectParameters();
$exApp = $this->service->getExApp($notification->getApp());
if ($exApp === null) {
throw new \InvalidArgumentException();
}
$fileActionMenu = $this->exFilesActionsMenuService->getExAppFileAction($notification->getApp(), $notification->getObjectId());
if ($fileActionMenu === null) {
throw new \InvalidArgumentException();
}

$notification->setLink($this->url->linkToRoute('files.view.index', ['fileid' => $subjectParameters['fileid']]));
if ($fileActionMenu->getIcon() !== '') {
$icon = $this->url->linkToOCSRouteAbsolute('app_ecosystem_v2.OCSApi.loadFileActionIcon', ['appId' => $notification->getApp(), 'exFileActionName' => $notification->getObjectId()]);
} else {
$icon = $this->url->imagePath(Application::APP_ID, 'app-dark.svg');
}
$notification->setIcon($icon);
$notification->setRichSubject($exApp->getName());
$notification->setRichMessage($l->t($subjectParameters['message']));
$notification->setParsedSubject($l->t($subjectParameters['message']));
return $notification;
$parameters = $notification->getSubjectParameters();
if (isset($parameters['link']) && $parameters['link'] !== '') {
$notification->setLink($parameters['link']);
}
$notification->setIcon($this->url->imagePath(Application::APP_ID, 'app-dark.svg'));

default:
throw new \InvalidArgumentException();
if (isset($parameters['rich_subject']) && isset($parameters['rich_subject_params'])) {
$notification->setRichSubject($parameters['rich_subject'], $parameters['rich_subject_params']);
}
if (isset($parameters['rich_message']) && isset($parameters['rich_message_params'])) {
$notification->setRichMessage($parameters['rich_message'], $parameters['rich_message_params']);
}

return $notification;
}
}
11 changes: 5 additions & 6 deletions lib/Notifications/ExNotificationsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,33 @@

namespace OCA\AppEcosystemV2\Notifications;

use OCA\AppEcosystemV2\Db\ExApp;
use OCP\Notification\IManager;
use OCP\Notification\INotification;

class ExNotificationsManager {
private IManager $manager;

public function __construct(IManager $manager) {
$this->manager = $manager;
}

/**
* Create a notification for ExApp and notify the user
*
* @param ExApp $exApp
* @param string $appId
* @param string|null $userId
* @param array $params
*
* @return INotification
*/
public function sendNotification(ExApp $exApp, ?string $userId = null, array $params = []): INotification {
public function sendNotification(string $appId, ?string $userId = null, array $params = []): INotification {
$notification = $this->manager->createNotification();
$notification
->setApp($exApp->getAppid())
->setApp($appId)
->setUser($userId)
->setDateTime(new \DateTime())
->setObject($params['object'], $params['object_id'])
->setSubject($params['subject'], $params['subject_params']);
// TODO: Define dynamic way for other options (e.g. notification actions)
->setSubject($params['subject_type'], $params['subject_params']);
$this->manager->notify($notification);
return $notification;
}
Expand Down
1 change: 1 addition & 0 deletions lib/Service/ExAppApiScopeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public function registerInitScopes(): bool {
['api_route' => $aeApiV1Prefix . '/ex-app/preference', 'scope_group' => 1, 'name' => 'BASIC'],
['api_route' => $aeApiV1Prefix . '/users', 'scope_group' => 2, 'name' => 'SYSTEM'],
['api_route' => $aeApiV1Prefix . '/ex-app/all', 'scope_group' => 2, 'name' => 'SYSTEM'],
['api_route' => $aeApiV1Prefix . '/notification', 'scope_group' => 12, 'name' => 'NOTIFICATIONS'],

// Cloud scopes
['api_route' => '/cloud/capabilities', 'scope_group' => 1, 'name' => 'BASIC'],
Expand Down
6 changes: 5 additions & 1 deletion lib/Service/ExFilesActionsMenuService.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ public function handleFileAction(string $userId, string $appId, string $fileActi
'actionFile' => [
'fileId' => $actionFile['fileId'],
'name' => $actionFile['name'],
'dir' => $actionFile['dir'],
'directory' => $actionFile['directory'],
'etag' => $actionFile['etag'],
'mime' => $actionFile['mime'],
'favorite' => $actionFile['favorite'] ?? "false",
'permissions' => $actionFile['permissions']
],
];
$exApp = $this->appEcosystemV2Service->getExApp($appId);
Expand Down
Loading

0 comments on commit 08b72fe

Please sign in to comment.