Skip to content

Commit 5de840c

Browse files
authored
IBX-9060: Added bulk mark-as-read for user notifications (#630)
1 parent 9862730 commit 5de840c

File tree

15 files changed

+326
-18
lines changed

15 files changed

+326
-18
lines changed

src/contracts/Persistence/Notification/Handler.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ public function createNotification(CreateStruct $createStruct): Notification;
3333
*/
3434
public function updateNotification(APINotification $notification, UpdateStruct $updateStruct): Notification;
3535

36+
/**
37+
* @param int[] $notificationIds
38+
*
39+
* @return int[]
40+
*/
41+
public function bulkUpdateUserNotifications(
42+
int $ownerId,
43+
UpdateStruct $updateStruct,
44+
bool $pendingOnly = false,
45+
array $notificationIds = []
46+
): array;
47+
3648
/**
3749
* Count users unread Notifications.
3850
*

src/contracts/Repository/Decorator/NotificationServiceDecorator.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717
abstract class NotificationServiceDecorator implements NotificationService
1818
{
19-
/** @var \Ibexa\Contracts\Core\Repository\NotificationService */
20-
protected $innerService;
19+
protected NotificationService $innerService;
2120

2221
public function __construct(NotificationService $innerService)
2322
{
@@ -41,6 +40,11 @@ public function getNotification(int $notificationId): Notification
4140
return $this->innerService->getNotification($notificationId);
4241
}
4342

43+
public function markUserNotificationsAsRead(array $notificationIds = []): void
44+
{
45+
$this->innerService->markUserNotificationsAsRead($notificationIds);
46+
}
47+
4448
public function markNotificationAsRead(Notification $notification): void
4549
{
4650
$this->innerService->markNotificationAsRead($notification);

src/contracts/Repository/NotificationService.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public function findNotifications(?NotificationQuery $query = null): Notificatio
3434
*/
3535
public function getNotification(int $notificationId): Notification;
3636

37+
/**
38+
* @param int[] $notificationIds
39+
*/
40+
public function markUserNotificationsAsRead(array $notificationIds = []): void;
41+
3742
/**
3843
* Mark notification as read so it no longer bother the user.
3944
*

src/lib/Persistence/Cache/NotificationHandler.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,37 @@ public function createNotification(CreateStruct $createStruct): Notification
3838
return $this->persistenceHandler->notificationHandler()->createNotification($createStruct);
3939
}
4040

41+
public function bulkUpdateUserNotifications(
42+
int $ownerId,
43+
UpdateStruct $updateStruct,
44+
bool $pendingOnly = false,
45+
array $notificationIds = []
46+
): array {
47+
$this->logger->logCall(__METHOD__, [
48+
'userId' => $ownerId,
49+
'notificationIds' => $notificationIds,
50+
]);
51+
52+
$updatedNotificationIds = $this->persistenceHandler
53+
->notificationHandler()
54+
->bulkUpdateUserNotifications($ownerId, $updateStruct, $pendingOnly, $notificationIds);
55+
56+
$cacheKeys = array_map(
57+
fn (int $id): string => $this->cacheIdentifierGenerator->generateKey(self::NOTIFICATION_IDENTIFIER, [$id], true),
58+
$updatedNotificationIds
59+
);
60+
61+
$cacheKeys[] = $this->cacheIdentifierGenerator->generateKey(
62+
self::NOTIFICATION_PENDING_COUNT_IDENTIFIER,
63+
[$ownerId],
64+
true
65+
);
66+
67+
$this->cache->deleteItems($cacheKeys);
68+
69+
return $updatedNotificationIds;
70+
}
71+
4172
/**
4273
* {@inheritdoc}
4374
*/

src/lib/Persistence/Legacy/Notification/Gateway.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use Ibexa\Contracts\Core\Persistence\Notification\CreateStruct;
1212
use Ibexa\Contracts\Core\Persistence\Notification\Notification;
13+
use Ibexa\Contracts\Core\Persistence\Notification\UpdateStruct;
1314
use Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery;
1415

1516
abstract class Gateway
@@ -42,6 +43,18 @@ abstract public function getNotificationById(int $notificationId): array;
4243
*/
4344
abstract public function updateNotification(Notification $notification): void;
4445

46+
/**
47+
* @param int[] $notificationIds
48+
*
49+
* @return int[]
50+
*/
51+
abstract public function bulkUpdateUserNotifications(
52+
int $ownerId,
53+
UpdateStruct $updateStruct,
54+
bool $pendingOnly = false,
55+
array $notificationIds = []
56+
): array;
57+
4558
abstract public function countUserNotifications(int $userId, ?NotificationQuery $query = null): int;
4659

4760
/**

src/lib/Persistence/Legacy/Notification/Gateway/DoctrineDatabase.php

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Doctrine\DBAL\Query\QueryBuilder;
1414
use Ibexa\Contracts\Core\Persistence\Notification\CreateStruct;
1515
use Ibexa\Contracts\Core\Persistence\Notification\Notification;
16+
use Ibexa\Contracts\Core\Persistence\Notification\UpdateStruct;
1617
use Ibexa\Contracts\Core\Repository\Values\Notification\Query\CriterionInterface;
1718
use Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery;
1819
use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
@@ -82,6 +83,78 @@ public function getNotificationById(int $notificationId): array
8283
return $query->execute()->fetchAllAssociative();
8384
}
8485

86+
/**
87+
* @param int[] $notificationIds
88+
*
89+
* @return int[]
90+
*/
91+
public function bulkUpdateUserNotifications(
92+
int $ownerId,
93+
UpdateStruct $updateStruct,
94+
bool $pendingOnly = false,
95+
array $notificationIds = []
96+
): array {
97+
$queryBuilder = $this->connection->createQueryBuilder();
98+
$this->applyNotificationFilters($queryBuilder, $ownerId, $pendingOnly, $notificationIds);
99+
100+
$idsToUpdate = $queryBuilder->execute()->fetchFirstColumn();
101+
if (empty($idsToUpdate)) {
102+
return [];
103+
}
104+
105+
if ($updateStruct->isPending !== null) {
106+
$this->updateNotificationsPendingStatus($idsToUpdate, $updateStruct->isPending);
107+
}
108+
109+
return array_map('intval', $idsToUpdate);
110+
}
111+
112+
/**
113+
* @param int[] $notificationIds
114+
*/
115+
private function applyNotificationFilters(
116+
QueryBuilder $queryBuilder,
117+
int $ownerId,
118+
bool $pendingOnly,
119+
array $notificationIds
120+
): void {
121+
$queryBuilder
122+
->select(self::COLUMN_ID)
123+
->from(self::TABLE_NOTIFICATION)
124+
->andWhere($queryBuilder->expr()->eq(self::COLUMN_OWNER_ID, ':ownerId'))
125+
->setParameter(':ownerId', $ownerId, ParameterType::INTEGER);
126+
127+
if ($pendingOnly) {
128+
$queryBuilder->andWhere($queryBuilder->expr()->eq(self::COLUMN_IS_PENDING, ':isPendingFlag'))
129+
->setParameter(':isPendingFlag', true, ParameterType::BOOLEAN);
130+
}
131+
132+
if (!empty($notificationIds)) {
133+
$queryBuilder->andWhere(
134+
$queryBuilder->expr()->in(self::COLUMN_ID, ':notificationIds')
135+
);
136+
$queryBuilder->setParameter(':notificationIds', $notificationIds, Connection::PARAM_INT_ARRAY);
137+
}
138+
}
139+
140+
/**
141+
* @param int[] $idsToUpdate
142+
*/
143+
private function updateNotificationsPendingStatus(array $idsToUpdate, bool $isPending): void
144+
{
145+
$updateQuery = $this->connection->createQueryBuilder();
146+
$updateQuery
147+
->update(self::TABLE_NOTIFICATION)
148+
->set(self::COLUMN_IS_PENDING, ':is_pending')
149+
->andWhere(
150+
$updateQuery->expr()->in(self::COLUMN_ID, ':ids')
151+
)
152+
->setParameter(':is_pending', $isPending, ParameterType::BOOLEAN)
153+
->setParameter(':ids', $idsToUpdate, Connection::PARAM_INT_ARRAY);
154+
155+
$updateQuery->execute();
156+
}
157+
85158
public function updateNotification(Notification $notification): void
86159
{
87160
if (!isset($notification->id) || !is_numeric($notification->id)) {
@@ -113,7 +186,7 @@ public function countUserNotifications(int $userId, ?NotificationQuery $query =
113186
$this->applyFilters($queryBuilder, $query->getCriteria());
114187
}
115188

116-
return (int)$queryBuilder->execute()->fetchColumn();
189+
return (int)$queryBuilder->execute()->fetchOne();
117190
}
118191

119192
public function countUserPendingNotifications(int $userId): int

src/lib/Persistence/Legacy/Notification/Gateway/ExceptionConversion.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Doctrine\DBAL\DBALException;
1212
use Ibexa\Contracts\Core\Persistence\Notification\CreateStruct;
1313
use Ibexa\Contracts\Core\Persistence\Notification\Notification;
14+
use Ibexa\Contracts\Core\Persistence\Notification\UpdateStruct;
1415
use Ibexa\Contracts\Core\Repository\Values\Notification\Query\NotificationQuery;
1516
use Ibexa\Core\Base\Exceptions\DatabaseException;
1617
use Ibexa\Core\Persistence\Legacy\Notification\Gateway;
@@ -44,6 +45,19 @@ public function getNotificationById(int $notificationId): array
4445
}
4546
}
4647

48+
public function bulkUpdateUserNotifications(
49+
int $ownerId,
50+
UpdateStruct $updateStruct,
51+
bool $pendingOnly = false,
52+
array $notificationIds = []
53+
): array {
54+
try {
55+
return $this->innerGateway->bulkUpdateUserNotifications($ownerId, $updateStruct, $pendingOnly, $notificationIds);
56+
} catch (DBALException | PDOException $e) {
57+
throw DatabaseException::wrap($e);
58+
}
59+
}
60+
4761
public function updateNotification(Notification $notification): void
4862
{
4963
try {

src/lib/Persistence/Legacy/Notification/Handler.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ public function getNotificationById(int $notificationId): Notification
7272
return reset($notification);
7373
}
7474

75+
public function bulkUpdateUserNotifications(
76+
int $ownerId,
77+
UpdateStruct $updateStruct,
78+
bool $pendingOnly = false,
79+
array $notificationIds = []
80+
): array {
81+
return $this->gateway->bulkUpdateUserNotifications($ownerId, $updateStruct, $pendingOnly, $notificationIds);
82+
}
83+
7584
/**
7685
* {@inheritdoc}
7786
*

src/lib/Repository/NotificationService.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@ public function getNotification(int $notificationId): APINotification
109109
return $this->buildDomainObject($notification);
110110
}
111111

112+
/**
113+
* @param int[] $notificationIds
114+
*/
115+
public function markUserNotificationsAsRead(array $notificationIds = []): void
116+
{
117+
$currentUserId = $this->getCurrentUserId();
118+
$updateStruct = new UpdateStruct();
119+
$updateStruct->isPending = false;
120+
121+
$this->persistenceHandler->bulkUpdateUserNotifications($currentUserId, $updateStruct, true, $notificationIds);
122+
}
123+
112124
public function markNotificationAsRead(APINotification $notification): void
113125
{
114126
$currentUserId = $this->getCurrentUserId();

src/lib/Repository/SiteAccessAware/NotificationService.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public function getNotification(int $notificationId): Notification
5050
return $this->service->getNotification($notificationId);
5151
}
5252

53+
/**
54+
* @param int[] $notificationIds
55+
*/
56+
public function markUserNotificationsAsRead(array $notificationIds = []): void
57+
{
58+
$this->service->markUserNotificationsAsRead($notificationIds);
59+
}
60+
5361
/**
5462
* Mark notification as read so it no longer bother the user.
5563
*

0 commit comments

Comments
 (0)