Skip to content
Open
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 lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public function getCapabilities(): array {
'user-status',
'exists',
'test-push',
'list-filter',
],
'push' => [
'devices',
Expand Down
13 changes: 12 additions & 1 deletion lib/Controller/EndpointController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ public function __construct(
* Get all notifications
*
* @param string $apiVersion Version of the API to use
* @param string $app Limit notifications to a given app
* @param string $objectType Limit notifications to a given object type
* @param string $objectId Limit notifications to a given object (only considered when object type is also provided)
* @return DataResponse<Http::STATUS_OK, list<NotificationsNotification>, array{'X-Nextcloud-User-Status': string}>|DataResponse<Http::STATUS_NO_CONTENT, null, array{X-Nextcloud-User-Status: string}>
*
* 200: Notifications returned
* 204: No app uses notifications
*/
#[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/notifications', requirements: ['apiVersion' => '(v1|v2)'])]
public function listNotifications(string $apiVersion): DataResponse {
public function listNotifications(string $apiVersion, string $app = '', string $objectType = '', string $objectId = ''): DataResponse {
$userStatus = $this->userStatusManager->getUserStatuses([
$this->getCurrentUser(),
]);
Expand All @@ -84,6 +87,14 @@ public function listNotifications(string $apiVersion): DataResponse {
$user = $this->session->getUser();
$filter = $this->manager->createNotification();
$filter->setUser($this->getCurrentUser());

if ($app !== '') {
$filter->setApp($app);
}
if ($objectType !== '') {
$filter->setObject($objectType, $objectId === '' ? Handler::FILTER_OBJECT_TYPE_ONLY : $objectId);
}

$language = $this->l10nFactory->getUserLanguage($user);
$notifications = $this->handler->get($filter);

Expand Down
4 changes: 3 additions & 1 deletion lib/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use OCP\Notification\INotification;

class Handler {
public const string FILTER_OBJECT_TYPE_ONLY = 'noone-uses-this-objectid-c72c4349-00d9-4376-95c2-b1fc90f668d9';

public function __construct(
protected IDBConnection $connection,
protected IManager $manager,
Expand Down Expand Up @@ -280,7 +282,7 @@ protected function sqlWhere(IQueryBuilder $sql, INotification $notification) {
$sql->andWhere($sql->expr()->eq('object_type', $sql->createNamedParameter($notification->getObjectType())));
}

if ($notification->getObjectId() !== '') {
if ($notification->getObjectId() !== '' && $notification->getObjectId() !== self::FILTER_OBJECT_TYPE_ONLY) {
$sql->andWhere($sql->expr()->eq('object_id', $sql->createNamedParameter($notification->getObjectId())));
}

Expand Down
27 changes: 27 additions & 0 deletions openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,33 @@
"default": "v2"
}
},
{
"name": "app",
"in": "query",
"description": "Limit notifications to a given app",
"schema": {
"type": "string",
"default": ""
}
},
{
"name": "objectType",
"in": "query",
"description": "Limit notifications to a given object type",
"schema": {
"type": "string",
"default": ""
}
},
{
"name": "objectId",
"in": "query",
"description": "Limit notifications to a given object (only considered when object type is also provided)",
"schema": {
"type": "string",
"default": ""
}
},
{
"name": "OCS-APIRequest",
"in": "header",
Expand Down
27 changes: 27 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,33 @@
"default": "v2"
}
},
{
"name": "app",
"in": "query",
"description": "Limit notifications to a given app",
"schema": {
"type": "string",
"default": ""
}
},
{
"name": "objectType",
"in": "query",
"description": "Limit notifications to a given object type",
"schema": {
"type": "string",
"default": ""
}
},
{
"name": "objectId",
"in": "query",
"description": "Limit notifications to a given object (only considered when object type is also provided)",
"schema": {
"type": "string",
"default": ""
}
},
{
"name": "OCS-APIRequest",
"in": "header",
Expand Down
1 change: 1 addition & 0 deletions tests/Unit/CapabilitiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public function testGetCapabilities(bool $webpush, bool $webpushBrowser, array $
'user-status',
'exists',
'test-push',
'list-filter',
],
'push' => $expected,
'admin-notifications' => [
Expand Down
64 changes: 64 additions & 0 deletions tests/Unit/Controller/EndpointControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,70 @@ public function testListNotificationsNoNotifiers(string $apiVersion): void {
$this->assertSame(Http::STATUS_NO_CONTENT, $response->getStatus());
}

public static function dataListNotificationsFilter(): array {
return [
'no filter' => ['', '', '', null, null],
'app only' => ['spreed', '', '', 'spreed', null],
'object type only' => ['', 'reminder', '', null, ['reminder', Handler::FILTER_OBJECT_TYPE_ONLY]],
'object type and id' => ['', 'reminder', 'token', null, ['reminder', 'token']],
'object id without type' => ['', '', 'token', null, null],
'app and object' => ['spreed', 'reminder', 'token', 'spreed', ['reminder', 'token']],
];
}

#[DataProvider(methodName: 'dataListNotificationsFilter')]
public function testListNotificationsFilter(string $app, string $objectType, string $objectId, ?string $expectedApp, ?array $expectedObject): void {
$controller = $this->getController();

$filter = $this->createMock(INotification::class);
$filter->expects($this->once())
->method('setUser')
->with('username')
->willReturn($filter);

if ($expectedApp === null) {
$filter->expects($this->never())
->method('setApp');
} else {
$filter->expects($this->once())
->method('setApp')
->with($expectedApp)
->willReturn($filter);
}

if ($expectedObject === null) {
$filter->expects($this->never())
->method('setObject');
} else {
$filter->expects($this->once())
->method('setObject')
->with($expectedObject[0], $expectedObject[1])
->willReturn($filter);
}

$this->manager->expects($this->once())
->method('hasNotifiers')
->willReturn(true);
$this->manager->expects($this->once())
->method('createNotification')
->willReturn($filter);

$this->l10nFactory
->method('getUserLanguage')
->with($this->user)
->willReturn('en');

$this->handler->expects($this->once())
->method('get')
->with($filter)
->willReturn([]);

$response = $controller->listNotifications('v2', $app, $objectType, $objectId);
$this->assertInstanceOf(DataResponse::class, $response);
$this->assertSame(Http::STATUS_OK, $response->getStatus());
$this->assertSame([], $response->getData());
}

public static function dataGetNotification(): array {
return [
['v1', 42, 'username1'],
Expand Down
51 changes: 51 additions & 0 deletions tests/Unit/HandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,57 @@ public function testDeleteById(): void {
$this->assertSame(0, $this->handler->count($limitedNotification), 'Wrong notification count for user1 after deleting');
}

public function testFilterByObject(): void {
$user = 'test_user1';
foreach ([['reminder', 'token1'], ['reminder', 'token2'], ['chat', 'token1']] as [$objectType, $objectId]) {
$this->handler->add($this->getNotification([
'getApp' => 'testing_notifications',
'getUser' => $user,
'getDateTime' => new \DateTime(),
'getObjectType' => $objectType,
'getObjectId' => $objectId,
'getSubject' => 'subject',
'getSubjectParameters' => [],
'getMessage' => 'message',
'getMessageParameters' => [],
'getLink' => 'https://example.tld/notification',
'getIcon' => 'https://example.tld/icon',
'getActions' => [],
]));
}

$unfiltered = $this->getNotification([
'getApp' => 'testing_notifications',
'getUser' => $user,
]);
$this->assertCount(3, $this->handler->get($unfiltered), 'Wrong notification count without an object filter');

$byTypeOnly = $this->getNotification([
'getApp' => 'testing_notifications',
'getUser' => $user,
'getObjectType' => 'reminder',
'getObjectId' => Handler::FILTER_OBJECT_TYPE_ONLY,
]);
$this->assertCount(2, $this->handler->get($byTypeOnly), 'Wrong notification count when filtering by object type only');
$this->assertSame(2, $this->handler->count($byTypeOnly), 'Wrong notification count when filtering by object type only');

$byTypeAndId = $this->getNotification([
'getApp' => 'testing_notifications',
'getUser' => $user,
'getObjectType' => 'reminder',
'getObjectId' => 'token1',
]);
$this->assertCount(1, $this->handler->get($byTypeAndId), 'Wrong notification count when filtering by object type and id');

$byOtherType = $this->getNotification([
'getApp' => 'testing_notifications',
'getUser' => $user,
'getObjectType' => 'chat',
'getObjectId' => Handler::FILTER_OBJECT_TYPE_ONLY,
]);
$this->assertCount(1, $this->handler->get($byOtherType), 'Wrong notification count when filtering by another object type');
}

protected function getNotification(array $values = []): INotification&MockObject {
$notification = $this->getMockBuilder(INotification::class)
->getMock();
Expand Down
Loading