Skip to content

Commit 180fac9

Browse files
Merge pull request #1973 from nextcloud/enh/add-rich-object-support-to-api
Add rich object support to API
2 parents 303685f + 78be17b commit 180fac9

12 files changed

Lines changed: 1000 additions & 273 deletions

File tree

appinfo/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
['name' => 'Endpoint#deleteAllNotifications', 'url' => '/api/{apiVersion}/notifications', 'verb' => 'DELETE', 'requirements' => ['apiVersion' => '(v1|v2)']],
1818

1919
['name' => 'API#generateNotification', 'url' => '/api/{apiVersion}/admin_notifications/{userId}', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v1|v2)']],
20+
['name' => 'API#generateNotificationV3', 'url' => '/api/{apiVersion3}/admin_notifications/{userId}', 'verb' => 'POST', 'requirements' => ['apiVersion3' => '(v3)']],
2021

2122
['name' => 'Settings#personal', 'url' => '/api/{apiVersion}/settings', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],
2223
['name' => 'Settings#admin', 'url' => '/api/{apiVersion}/settings/admin', 'verb' => 'POST', 'requirements' => ['apiVersion' => '(v2)']],

lib/App.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Console\Output\OutputInterface;
1818

1919
class App implements IDeferrableApp {
20+
protected ?int $lastInsertedId = null;
2021
public function __construct(
2122
protected Handler $handler,
2223
protected Push $push,
@@ -33,15 +34,19 @@ public function setOutput(OutputInterface $output): void {
3334
* @since 8.2.0
3435
*/
3536
public function notify(INotification $notification): void {
36-
$notificationId = $this->handler->add($notification);
37+
$this->lastInsertedId = $this->handler->add($notification);
3738

3839
try {
39-
$this->push->pushToDevice($notificationId, $notification);
40+
$this->push->pushToDevice($this->lastInsertedId, $notification);
4041
} catch (NotificationNotFoundException $e) {
4142
$this->logger->error('Error while preparing push notification', ['exception' => $e]);
4243
}
4344
}
4445

46+
public function getLastInsertedId(): ?int {
47+
return $this->lastInsertedId;
48+
}
49+
4550
/**
4651
* @param INotification $notification
4752
* @return int

lib/Controller/APIController.php

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace OCA\Notifications\Controller;
1111

12+
use OCA\Notifications\App;
13+
use OCA\Notifications\ResponseDefinitions;
1214
use OCP\AppFramework\Http;
1315
use OCP\AppFramework\Http\Attribute\OpenAPI;
1416
use OCP\AppFramework\Http\DataResponse;
@@ -18,65 +20,136 @@
1820
use OCP\IUser;
1921
use OCP\IUserManager;
2022
use OCP\Notification\IManager;
23+
use OCP\Notification\IncompleteNotificationException;
24+
use OCP\Notification\InvalidValueException;
25+
use OCP\RichObjectStrings\InvalidObjectExeption;
26+
use OCP\RichObjectStrings\IValidator;
27+
use Psr\Log\LoggerInterface;
2128

29+
/**
30+
* @psalm-import-type NotificationsRichObjectParameter from ResponseDefinitions
31+
*/
2232
class APIController extends OCSController {
2333
public function __construct(
2434
string $appName,
2535
IRequest $request,
2636
protected ITimeFactory $timeFactory,
2737
protected IUserManager $userManager,
2838
protected IManager $notificationManager,
39+
protected App $notificationApp,
40+
protected IValidator $richValidator,
41+
protected LoggerInterface $logger,
2942
) {
3043
parent::__construct($appName, $request);
3144
}
3245

3346
/**
34-
* Generate a notification for a user
47+
* Generate a notification for a user (deprecated, use v3 instead)
3548
*
3649
* @param string $userId ID of the user
3750
* @param string $shortMessage Subject of the notification
3851
* @param string $longMessage Message of the notification
3952
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, null, array{}>
53+
* @deprecated 30.0.0
4054
*
4155
* 200: Notification generated successfully
4256
* 400: Generating notification is not possible
4357
* 404: User not found
4458
*/
4559
#[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)]
4660
public function generateNotification(string $userId, string $shortMessage, string $longMessage = ''): DataResponse {
61+
$response = $this->generateNotificationV3($userId, $shortMessage, $longMessage);
62+
if ($response->getStatus() === Http::STATUS_OK) {
63+
return new DataResponse();
64+
}
65+
66+
// Translate to old status code
67+
$error = $response->getData()['error'] ?? null;
68+
$code = match($error) {
69+
'user' => Http::STATUS_NOT_FOUND,
70+
'subject',
71+
'message' => Http::STATUS_BAD_REQUEST,
72+
default => Http::STATUS_INTERNAL_SERVER_ERROR,
73+
};
74+
return new DataResponse(null, $code);
75+
}
76+
77+
/**
78+
* Generate a notification with rich object parameters for a user
79+
*
80+
* @param string $userId ID of the user
81+
* @param string $subject Subject of the notification
82+
* @param string $message Message of the notification
83+
* @param array<string, NotificationsRichObjectParameter> $subjectParameters Rich objects to fill the subject placeholders, {@see \OCP\RichObjectStrings\Definitions}
84+
* @param array<string, NotificationsRichObjectParameter> $messageParameters Rich objects to fill the message placeholders, {@see \OCP\RichObjectStrings\Definitions}
85+
* @return DataResponse<Http::STATUS_OK, array{id: int}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
86+
*
87+
* 200: Notification generated successfully, returned id is the notification ID for future delete requests
88+
* 400: Provided data was invalid, check error field of the response of log file for details
89+
*/
90+
#[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)]
91+
public function generateNotificationV3(
92+
string $userId,
93+
string $subject = '',
94+
string $message = '',
95+
array $subjectParameters = [],
96+
array $messageParameters = [],
97+
): DataResponse {
4798
$user = $this->userManager->get($userId);
4899

49100
if (!$user instanceof IUser) {
50-
return new DataResponse(null, Http::STATUS_NOT_FOUND);
101+
return new DataResponse(['error' => 'user'], Http::STATUS_BAD_REQUEST);
51102
}
52103

53-
if ($shortMessage === '' || strlen($shortMessage) > 255) {
54-
return new DataResponse(null, Http::STATUS_BAD_REQUEST);
104+
if ($subject === '' || strlen($subject) > 255) {
105+
return new DataResponse(['error' => 'subject'], Http::STATUS_BAD_REQUEST);
55106
}
56107

57-
if ($longMessage !== '' && strlen($longMessage) > 4000) {
58-
return new DataResponse(null, Http::STATUS_BAD_REQUEST);
108+
if ($message !== '' && strlen($message) > 4000) {
109+
return new DataResponse(['error' => 'message'], Http::STATUS_BAD_REQUEST);
59110
}
60111

61112
$notification = $this->notificationManager->createNotification();
62113
$datetime = $this->timeFactory->getDateTime();
63114

64115
try {
116+
if (!empty($subjectParameters)) {
117+
$this->richValidator->validate($subject, $subjectParameters);
118+
}
119+
if ($message !== '' && !empty($messageParameters)) {
120+
$this->richValidator->validate($message, $messageParameters);
121+
}
65122
$notification->setApp('admin_notifications')
66123
->setUser($user->getUID())
67124
->setDateTime($datetime)
68125
->setObject('admin_notifications', dechex($datetime->getTimestamp()))
69-
->setSubject('ocs', [$shortMessage]);
126+
->setSubject(
127+
'ocs',
128+
[
129+
'subject' => $subject,
130+
'parameters' => $subjectParameters,
131+
]
132+
);
70133

71-
if ($longMessage !== '') {
72-
$notification->setMessage('ocs', [$longMessage]);
134+
if ($message !== '') {
135+
$notification->setMessage(
136+
'ocs',
137+
[
138+
'message' => $message,
139+
'parameters' => $messageParameters,
140+
]
141+
);
73142
}
74143

75144
$this->notificationManager->notify($notification);
76-
} catch (\InvalidArgumentException) {
77-
return new DataResponse(null, Http::STATUS_INTERNAL_SERVER_ERROR);
145+
} catch (InvalidObjectExeption $e) {
146+
$this->logger->error('Invalid rich object parameter provided: ' . $e->getMessage(), ['exception' => $e]);
147+
return new DataResponse(['error' => 'parameters'], Http::STATUS_BAD_REQUEST);
148+
} catch (InvalidValueException|IncompleteNotificationException $e) {
149+
$this->logger->error('Invalid value for notification provided: ' . $e->getMessage(), ['exception' => $e]);
150+
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
78151
}
79152

80-
return new DataResponse();
153+
return new DataResponse(['id' => (int) $this->notificationApp->getLastInsertedId()]);
81154
}
82155
}

lib/Notifier/AdminNotifications.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,22 @@ public function prepare(INotification $notification, string $languageCode): INot
176176
case 'cli':
177177
case 'ocs':
178178
$subjectParams = $notification->getSubjectParameters();
179-
$notification->setParsedSubject($subjectParams[0]);
179+
if (isset($subjectParams['subject'])) {
180+
// Nextcloud 30+
181+
$notification->setRichSubject($subjectParams['subject'], $subjectParams['parameters']);
182+
} else {
183+
// Legacy before Nextcloud 30 (v3)
184+
$notification->setParsedSubject($subjectParams[0]);
185+
}
180186
$messageParams = $notification->getMessageParameters();
181-
if (isset($messageParams[0]) && $messageParams[0] !== '') {
182-
$notification->setParsedMessage($messageParams[0]);
187+
if (!empty($messageParams)) {
188+
if (!empty($messageParams['message'])) {
189+
// Nextcloud 30+
190+
$notification->setRichMessage($messageParams['message'], $messageParams['parameters']);
191+
} elseif (!empty($messageParams[0])) {
192+
// Legacy before Nextcloud 30 (v3)
193+
$notification->setParsedMessage($messageParams[0]);
194+
}
183195
}
184196

185197
$notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('notifications', 'notifications-dark.svg')));

lib/ResponseDefinitions.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,36 @@
1010
namespace OCA\Notifications;
1111

1212
/**
13+
* @psalm-type NotificationsRichObjectParameter = array{
14+
* type: string,
15+
* id: string,
16+
* name: string,
17+
* server?: string,
18+
* link?: string,
19+
* 'call-type'?: 'one2one'|'group'|'public',
20+
* 'icon-url'?: string,
21+
* 'message-id'?: string,
22+
* boardname?: string,
23+
* stackname?: string,
24+
* size?: string,
25+
* path?: string,
26+
* mimetype?: string,
27+
* 'preview-available'?: 'yes'|'no',
28+
* mtime?: string,
29+
* latitude?: string,
30+
* longitude?: string,
31+
* description?: string,
32+
* thumb?: string,
33+
* website?: string,
34+
* visibility?: '0'|'1',
35+
* assignable?: '0'|'1',
36+
* conversation?: string,
37+
* etag?: string,
38+
* permissions?: string,
39+
* width?: string,
40+
* height?: string,
41+
* }
42+
*
1343
* @psalm-type NotificationsNotificationAction = array{
1444
* label: string,
1545
* link: string,
@@ -29,9 +59,9 @@
2959
* link: string,
3060
* actions: NotificationsNotificationAction[],
3161
* subjectRich?: string,
32-
* subjectRichParameters?: array<string, mixed>,
62+
* subjectRichParameters?: array<string, NotificationsRichObjectParameter>,
3363
* messageRich?: string,
34-
* messageRichParameters?: array<string, mixed>,
64+
* messageRichParameters?: array<string, NotificationsRichObjectParameter>,
3565
* icon?: string,
3666
* shouldNotify?: bool,
3767
* }

0 commit comments

Comments
 (0)