diff --git a/application/src/main/data/upgrade/3.4.3/schema_update.sql b/application/src/main/data/upgrade/3.4.3/schema_update.sql index 89f993b63e..55a972cc42 100644 --- a/application/src/main/data/upgrade/3.4.3/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.3/schema_update.sql @@ -19,7 +19,8 @@ CREATE TABLE IF NOT EXISTS notification_target ( created_time BIGINT NOT NULL, tenant_id UUID NULL CONSTRAINT fk_notification_target_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, - configuration VARCHAR(10000) NOT NULL + configuration VARCHAR(10000) NOT NULL, + CONSTRAINT uq_notification_target_name UNIQUE (tenant_id, name) ); CREATE INDEX IF NOT EXISTS idx_notification_target_tenant_id_created_time ON notification_target(tenant_id, created_time DESC); @@ -29,7 +30,8 @@ CREATE TABLE IF NOT EXISTS notification_template ( tenant_id UUID NULL CONSTRAINT fk_notification_template_tenant_id REFERENCES tenant(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, notification_type VARCHAR(32) NOT NULL, - configuration VARCHAR(10000) NOT NULL + configuration VARCHAR(10000) NOT NULL, + CONSTRAINT uq_notification_template_name UNIQUE (tenant_id, name) ); CREATE INDEX IF NOT EXISTS idx_notification_template_tenant_id_created_time ON notification_template(tenant_id, created_time DESC); @@ -41,7 +43,8 @@ CREATE TABLE IF NOT EXISTS notification_rule ( template_id UUID NOT NULL CONSTRAINT fk_notification_rule_template_id REFERENCES notification_template(id), trigger_type VARCHAR(50) NOT NULL, trigger_config VARCHAR(1000) NOT NULL, - recipients_config VARCHAR(10000) NOT NULL + recipients_config VARCHAR(10000) NOT NULL, + CONSTRAINT uq_notification_rule_name UNIQUE (tenant_id, name) ); CREATE INDEX IF NOT EXISTS idx_notification_rule_tenant_id_created_time ON notification_rule(tenant_id, created_time DESC); @@ -71,6 +74,7 @@ CREATE TABLE IF NOT EXISTS notification ( type VARCHAR(32) NOT NULL, subject VARCHAR(255), text VARCHAR(1000) NOT NULL, + additional_config VARCHAR(500), info VARCHAR(1000), status VARCHAR(32) ) PARTITION BY RANGE (created_time); diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java index 66c1940be4..a57fe18264 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -90,6 +90,8 @@ public class NotificationController extends BaseController { "```\n{\n \"unsubCmd\": {\n \"cmdId\": 1234\n }\n}\n```\n" + "To mark certain notifications as read, use following command:\n" + "```\n{\n \"markAsReadCmd\": {\n \"cmdId\": 1234,\n \"notifications\": [\n \"6f860330-7fc2-11ed-b855-7dd3b7d2faa9\",\n \"5b6dfee0-8d0d-11ed-b61f-35a57b03dade\"\n ]\n }\n}\n\n```\n" + + "To mark all notifications as read:\n" + + "```\n{\n \"markAllAsReadCmd\": {\n \"cmdId\": 1234\n }\n}\n```\n" + "\n\n" + "Update structure for unread **notifications count subscription**:\n" + "```\n{\n \"cmdId\": 1234,\n \"totalUnreadCount\": 55\n}\n```\n" + @@ -136,7 +138,7 @@ public class NotificationController extends BaseController { @RequestParam(defaultValue = "false") boolean unreadOnly, @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return notificationService.findNotificationsByUserIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink); + return notificationService.findNotificationsByRecipientIdAndReadStatus(user.getTenantId(), user.getId(), unreadOnly, pageLink); } @PutMapping("/notification/{id}/read") @@ -147,6 +149,12 @@ public class NotificationController extends BaseController { notificationCenter.markNotificationAsRead(user.getTenantId(), user.getId(), notificationId); } + @PutMapping("/notifications/read") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public void markAllNotificationsAsRead(@AuthenticationPrincipal SecurityUser user) { + notificationCenter.markAllNotificationsAsRead(user.getTenantId(), user.getId()); + } + @DeleteMapping("/notification/{id}") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") public void deleteNotification(@PathVariable UUID id, diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 55381a5f94..12f31240b6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -55,11 +55,13 @@ import java.io.IOException; import java.net.URI; import java.security.InvalidParameterException; import java.util.Optional; +import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Semaphore; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS; @@ -140,7 +142,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke if (!checkLimits(session, sessionRef)) { return; } - internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef)); + var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef); + internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, + tenantProfileConfiguration != null && tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ? + tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500)); externalSessionMap.put(externalSessionId, internalSessionId); processInWebSocketService(sessionRef, SessionEvent.onEstablished()); @@ -215,21 +220,22 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke private final RemoteEndpoint.Async asyncRemote; private final WebSocketSessionRef sessionRef; - // TODO: carefully review ( + discuss removal of the msgQueue) - private final Semaphore sendingSemaphore = new Semaphore(1); + private final AtomicBoolean isSending = new AtomicBoolean(false); + private final Queue> msgQueue; private volatile long lastActivityTime; - SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef) { + SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) { super(); this.session = session; Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class); this.asyncRemote = nativeSession.getAsyncRemote(); this.sessionRef = sessionRef; + this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession); this.lastActivityTime = System.currentTimeMillis(); } - void sendPing(long currentTime) { + synchronized void sendPing(long currentTime) { try { long timeSinceLastActivity = currentTime - lastActivityTime; if (timeSinceLastActivity >= pingTimeout) { @@ -244,37 +250,40 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } } - void closeSession(CloseStatus reason) { + private void closeSession(CloseStatus reason) { try { close(this.sessionRef, reason); } catch (IOException ioe) { log.trace("[{}] Session transport error", session.getId(), ioe); - } finally { - sendingSemaphore.release(); } } - void processPongMessage(long currentTime) { + synchronized void processPongMessage(long currentTime) { lastActivityTime = currentTime; } - void sendMsg(String msg) { + synchronized void sendMsg(String msg) { sendMsg(new TbWebSocketTextMsg(msg)); } - void sendMsg(TbWebSocketMsg msg) { - try { - sendingSemaphore.acquire(); + synchronized void sendMsg(TbWebSocketMsg msg) { + if (isSending.compareAndSet(false, true)) { sendMsgInternal(msg); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (Exception e) { - sendingSemaphore.release(); - throw e; + } else { + try { + msgQueue.add(msg); + } catch (RuntimeException e) { + if (log.isTraceEnabled()) { + log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e); + } else { + log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); + } + closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!")); + } } } - void sendMsgInternal(TbWebSocketMsg msg) { + private void sendMsgInternal(TbWebSocketMsg msg) { try { if (TbWebSocketMsgType.TEXT.equals(msg.getType())) { TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg; @@ -282,6 +291,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke } else { TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg; this.asyncRemote.sendPing(pingMsg.getMsg()); + processNextMsg(); } } catch (Exception e) { log.trace("[{}] Failed to send msg", session.getId(), e); @@ -295,7 +305,16 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke log.trace("[{}] Failed to send msg", session.getId(), result.getException()); closeSession(CloseStatus.SESSION_NOT_RELIABLE); } else { - sendingSemaphore.release(); + processNextMsg(); + } + } + + private void processNextMsg() { + TbWebSocketMsg msg = msgQueue.poll(); + if (msg != null) { + sendMsgInternal(msg); + } else { + isSending.set(false); } } } @@ -476,4 +495,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); } + private DefaultTenantProfileConfiguration getTenantProfileConfiguration(WebSocketSessionRef sessionRef) { + return Optional.ofNullable(tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId())) + .map(TenantProfile::getDefaultProfileConfiguration).orElse(null); + } + } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java index c36ca25e24..032add9d82 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java @@ -140,7 +140,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple return notificationTargetService.findRecipientsForNotificationTarget(tenantId, ctx.getCustomerId(), new NotificationTargetId(targetId), pageLink); }, 200, recipientsBatch -> { for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { - if (deliveryMethod.isIndependent()) continue; + if (deliveryMethod.isStandalone()) continue; List recipients = recipientsBatch.getData(); log.debug("Sending {} notifications for request {} to recipients batch ({})", deliveryMethod, savedNotificationRequest.getId(), recipients.size()); @@ -158,7 +158,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple }); } for (NotificationDeliveryMethod deliveryMethod : deliveryMethods) { - if (deliveryMethod.isIndependent()) { + if (deliveryMethod.isStandalone()) { NotificationChannel notificationChannel = channels.get(deliveryMethod); ListenableFuture resultFuture = process(notificationChannel, null, ctx); DonAsynchron.withCallback(resultFuture, result -> { @@ -221,6 +221,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple .type(ctx.getNotificationTemplate().getNotificationType()) .subject(processedTemplate.getSubject()) .text(processedTemplate.getBody()) + .additionalConfig(processedTemplate.getAdditionalConfig()) .info(request.getInfo()) .status(NotificationStatus.SENT) .build(); @@ -260,7 +261,7 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple public void markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { boolean updated = notificationService.markNotificationAsRead(tenantId, recipientId, notificationId); if (updated) { - log.debug("Marking notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); + log.trace("Marked notification {} as read (recipient id: {}, tenant id: {})", notificationId, recipientId, tenantId); NotificationUpdate update = NotificationUpdate.builder() .notificationId(notificationId) .updatedStatus(NotificationStatus.READ) @@ -270,6 +271,20 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple } } + @Override + public void markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) { + int updatedCount = notificationService.markAllNotificationsAsRead(tenantId, recipientId); + if (updatedCount > 0) { + log.trace("Marked all notifications as read (recipient id: {}, tenant id: {})", recipientId, tenantId); + NotificationUpdate update = NotificationUpdate.builder() + .allNotifications(true) + .updatedStatus(NotificationStatus.READ) + .updateType(ComponentLifecycleEvent.UPDATED) + .build(); + onNotificationUpdate(tenantId, recipientId, update); + } + } + @Override public void deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) { Notification notification = notificationService.findNotificationById(tenantId, notificationId); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java index 0072028353..d0f3adbf58 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java @@ -174,6 +174,7 @@ public class DefaultWebSocketService implements WebSocketService { newCmdHandler(NotificationCmdsWrapper::getUnreadSubCmd, notificationCmdsHandler::handleUnreadNotificationsSubCmd), newCmdHandler(NotificationCmdsWrapper::getUnreadCountSubCmd, notificationCmdsHandler::handleUnreadNotificationsCountSubCmd), newCmdHandler(NotificationCmdsWrapper::getMarkAsReadCmd, notificationCmdsHandler::handleMarkAsReadCmd), + newCmdHandler(NotificationCmdsWrapper::getMarkAllAsReadCmd, notificationCmdsHandler::handleMarkAllAsReadCmd), newCmdHandler(NotificationCmdsWrapper::getUnsubCmd, notificationCmdsHandler::handleUnsubCmd) ); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java index 3bfe888b7f..3dc3670b94 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java @@ -34,6 +34,7 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.ws.WebSocketService; import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; @@ -101,7 +102,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void fetchUnreadNotifications(NotificationsSubscription subscription) { log.trace("[{}, subId: {}] Fetching unread notifications from DB", subscription.getSessionId(), subscription.getSubscriptionId()); - PageData notifications = notificationService.findLatestUnreadNotificationsByUserId(subscription.getTenantId(), + PageData notifications = notificationService.findLatestUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId(), subscription.getLimit()); subscription.getLatestUnreadNotifications().clear(); notifications.getData().forEach(notification -> { @@ -112,7 +113,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void fetchUnreadNotificationsCount(NotificationsCountSubscription subscription) { log.trace("[{}, subId: {}] Fetching unread notifications count from DB", subscription.getSessionId(), subscription.getSubscriptionId()); - int unreadCount = notificationService.countUnreadNotificationsByUserId(subscription.getTenantId(), (UserId) subscription.getEntityId()); + int unreadCount = notificationService.countUnreadNotificationsByRecipientId(subscription.getTenantId(), (UserId) subscription.getEntityId()); subscription.getUnreadCounter().set(unreadCount); } @@ -129,7 +130,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH private void handleNotificationUpdate(NotificationsSubscription subscription, NotificationUpdate update) { log.trace("[{}, subId: {}] Handling notification update: {}", subscription.getSessionId(), subscription.getSubscriptionId(), update); Notification notification = update.getNotification(); - UUID notificationId = update.getNotificationId().getId(); + UUID notificationId = update.getNotificationId(); switch (update.getUpdateType()) { case CREATED: { subscription.getLatestUnreadNotifications().put(notificationId, notification); @@ -144,7 +145,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH } case UPDATED: { if (update.getUpdatedStatus() == NotificationStatus.READ) { - if (subscription.getLatestUnreadNotifications().containsKey(notificationId)) { + if (update.isAllNotifications() || subscription.getLatestUnreadNotifications().containsKey(notificationId)) { fetchUnreadNotifications(subscription); sendUpdate(subscription.getSessionId(), subscription.createFullUpdate()); } else { @@ -234,10 +235,15 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH .map(NotificationId::new) .forEach(notificationId -> { notificationCenter.markNotificationAsRead(securityCtx.getTenantId(), securityCtx.getId(), notificationId); - // fixme: should send bulk update event, not a separate event for each notification }); } + @Override + public void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd) { + SecurityUser securityCtx = sessionRef.getSecurityCtx(); + notificationCenter.markAllNotificationsAsRead(securityCtx.getTenantId(), securityCtx.getId()); + } + @Override public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) { localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId()); diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java index 0f57730601..53cfa1bdb3 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/NotificationCommandsHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.ws.notification; import org.thingsboard.server.service.ws.WebSocketSessionRef; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsSubCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; @@ -29,6 +30,8 @@ public interface NotificationCommandsHandler { void handleMarkAsReadCmd(WebSocketSessionRef sessionRef, MarkNotificationsAsReadCmd cmd); + void handleMarkAllAsReadCmd(WebSocketSessionRef sessionRef, MarkAllNotificationsAsReadCmd cmd); + void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd); } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java new file mode 100644 index 0000000000..4c469813dd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/MarkAllNotificationsAsReadCmd.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ws.notification.cmd; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MarkAllNotificationsAsReadCmd implements WsCmd { + private int cmdId; +} diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java index 1c56d49a6f..4e0bc9f4de 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/cmd/NotificationCmdsWrapper.java @@ -26,6 +26,8 @@ public class NotificationCmdsWrapper { private MarkNotificationsAsReadCmd markAsReadCmd; + private MarkAllNotificationsAsReadCmd markAllAsReadCmd; + private NotificationsUnsubCmd unsubCmd; } diff --git a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java index 0dfb44e805..fc01ba41e7 100644 --- a/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/ws/notification/sub/NotificationUpdate.java @@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import java.util.UUID; + @Data @NoArgsConstructor @AllArgsConstructor @@ -31,12 +33,16 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; public class NotificationUpdate { private NotificationId notificationId; - private NotificationStatus updatedStatus; private Notification notification; + + boolean allNotifications; + + private NotificationStatus updatedStatus; private ComponentLifecycleEvent updateType; - public NotificationId getNotificationId() { - return notificationId != null ? notificationId : notification.getId(); + public UUID getNotificationId() { + return notificationId != null ? notificationId.getId() : + notification != null ? notification.getUuidId() : null; } } diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index 8b9d40cc51..2b1c34d974 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -126,7 +126,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest String text, NotificationDeliveryMethod... deliveryMethods) { NotificationTemplate notificationTemplate = new NotificationTemplate(); notificationTemplate.setTenantId(tenantId); - notificationTemplate.setName("Notification template for testing"); + notificationTemplate.setName("Notification template: " + text); notificationTemplate.setNotificationType(notificationType); NotificationTemplateConfig config = new NotificationTemplateConfig(); config.setDefaultTextTemplate(text); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java index 07b490d4a0..44c02ce36a 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java @@ -22,6 +22,7 @@ import org.java_websocket.client.WebSocketClient; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationTargetId; @@ -64,7 +65,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -74,9 +74,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @DaoSqlTest @@ -154,10 +152,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Test public void testReceivingNotificationUpdates_multipleSessions() throws Exception { connectOtherWsClient(); - wsClient.subscribeForUnreadNotifications(10); - otherWsClient.subscribeForUnreadNotifications(10); - wsClient.waitForReply(true); - otherWsClient.waitForReply(true); + wsClient.subscribeForUnreadNotifications(10).waitForReply(true); + otherWsClient.subscribeForUnreadNotifications(10).waitForReply(true); UnreadNotificationsUpdate notificationsUpdate = wsClient.getLastDataUpdate(); assertThat(notificationsUpdate.getTotalUnreadCount()).isZero(); @@ -213,6 +209,26 @@ public class NotificationApiTest extends AbstractNotificationApiTest { assertThat(otherWsClient.getLastCountUpdate().getTotalUnreadCount()).isOne(); } + @Test + public void testMarkingAllAsRead() { + wsClient.subscribeForUnreadNotifications(10).waitForReply(true); + NotificationTarget target = createNotificationTarget(customerUserId); + int notificationsCount = 20; + wsClient.registerWaitForUpdate(notificationsCount); + for (int i = 1; i <= notificationsCount; i++) { + submitNotificationRequest(target.getId(), "Test " + i, NotificationDeliveryMethod.PUSH); + } + wsClient.waitForUpdate(true); + assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isEqualTo(notificationsCount); + + wsClient.registerWaitForUpdate(1); + wsClient.markAllNotificationsAsRead(); + wsClient.waitForUpdate(true); + + assertThat(wsClient.getLastDataUpdate().getNotifications()).isEmpty(); + assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isZero(); + } + @Test public void testDelayedNotificationRequest() throws Exception { wsClient.subscribeForUnreadNotifications(5); @@ -285,7 +301,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { @Test public void testNotificationUpdatesForALotOfUsers() throws Exception { - int usersCount = 100; + int usersCount = 80; Map sessions = new HashMap<>(); List targets = new ArrayList<>(); @@ -311,7 +327,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest { sessions.forEach((user, wsClient) -> wsClient.registerWaitForUpdate(2)); NotificationRequest notificationRequest = submitNotificationRequest(targets, "Hello, ${email}", 0, - NotificationDeliveryMethod.PUSH, NotificationDeliveryMethod.EMAIL); + NotificationDeliveryMethod.PUSH); await().atMost(10, TimeUnit.SECONDS) .pollDelay(1, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS) .until(() -> { @@ -322,7 +338,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest { System.err.println("WS sessions received update: " + receivedUpdate); return receivedUpdate == sessions.size(); }); - verify(mailService, timeout(1500).times(usersCount)).sendEmail(eq(tenantId), startsWith("test-user-"), any(), any()); sessions.forEach((user, wsClient) -> { assertThat(wsClient.getLastDataUpdate().getTotalUnreadCount()).isOne(); @@ -338,7 +353,6 @@ public class NotificationApiTest extends AbstractNotificationApiTest { .until(() -> findNotificationRequest(notificationRequest.getId()).isSent()); NotificationRequestStats stats = getStats(notificationRequest.getId()); assertThat(stats.getSent().get(NotificationDeliveryMethod.PUSH)).hasValue(usersCount); - assertThat(stats.getSent().get(NotificationDeliveryMethod.EMAIL)).hasValue(usersCount); sessions.values().forEach(wsClient -> wsClient.registerWaitForUpdate(2)); deleteNotificationRequest(notificationRequest.getId()); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java index 7a7c835a6a..7e8e4ebc99 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationApiWsClient.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.RandomUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.service.ws.notification.cmd.MarkAllNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.MarkNotificationsAsReadCmd; import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper; import org.thingsboard.server.service.ws.notification.cmd.NotificationsCountSubCmd; @@ -73,6 +74,12 @@ public class NotificationApiWsClient extends TbTestWebSocketClient { sendCmd(cmdsWrapper); } + public void markAllNotificationsAsRead() { + NotificationCmdsWrapper cmdsWrapper = new NotificationCmdsWrapper(); + cmdsWrapper.setMarkAllAsReadCmd(new MarkAllNotificationsAsReadCmd(newCmdId())); + sendCmd(cmdsWrapper); + } + public void sendCmd(NotificationCmdsWrapper cmdsWrapper) { String cmd = JacksonUtil.toString(cmdsWrapper); send(cmd); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java index e034deede8..7a19d35992 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationService.java @@ -30,16 +30,18 @@ public interface NotificationService { Notification findNotificationById(TenantId tenantId, NotificationId notificationId); - boolean markNotificationAsRead(TenantId tenantId, UserId userId, NotificationId notificationId); + boolean markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId); - PageData findNotificationsByUserIdAndReadStatus(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); + int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId); - PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit); + PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink); - int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId); + PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit); + + int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId); void updateNotificationsStatusByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status); - boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId); + boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java index cf987bc314..1b0a00a5ab 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/Notification.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.notification; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -40,6 +41,7 @@ public class Notification extends BaseData { private NotificationType type; private String subject; private String text; + private JsonNode additionalConfig; private NotificationInfo info; private NotificationStatus status; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java index f34c375634..abb184ec9f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java @@ -25,14 +25,14 @@ public enum NotificationDeliveryMethod { SLACK(true); @Getter - private final boolean independent; // TODO: think of a better name + private final boolean standalone; // means that notifications for the delivery method are sent independently of specified recipients NotificationDeliveryMethod() { this(false); } - NotificationDeliveryMethod(boolean independent) { - this.independent = independent; + NotificationDeliveryMethod(boolean standalone) { + this.standalone = standalone; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java index 408b8086ba..840ce638d8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/PushDeliveryMethodNotificationTemplate.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.notification.template; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -28,14 +29,12 @@ import org.thingsboard.server.common.data.notification.NotificationDeliveryMetho public class PushDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { private String subject; - private String icon; - private String actionButtonConfig; + private JsonNode additionalConfig; public PushDeliveryMethodNotificationTemplate(PushDeliveryMethodNotificationTemplate other) { super(other); this.subject = other.subject; - this.icon = other.icon; - this.actionButtonConfig = other.actionButtonConfig; + this.additionalConfig = other.additionalConfig; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 4819316391..ad548b93f9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -659,6 +659,7 @@ public class ModelConstants { public static final String NOTIFICATION_TYPE_PROPERTY = "type"; public static final String NOTIFICATION_SUBJECT_PROPERTY = "subject"; public static final String NOTIFICATION_TEXT_PROPERTY = "text"; + public static final String NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; public static final String NOTIFICATION_STATUS_PROPERTY = "status"; public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java index bb76e07add..0f0c1a790e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationEntity.java @@ -62,6 +62,10 @@ public class NotificationEntity extends BaseSqlEntity { @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY, nullable = false) private String text; + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_ADDITIONAL_CONFIG_PROPERTY) + private JsonNode additionalConfig; + @Type(type = "json") @Formula("(SELECT r.info FROM notification_request r WHERE r.id = request_id)") private JsonNode info; @@ -80,6 +84,7 @@ public class NotificationEntity extends BaseSqlEntity { setType(notification.getType()); setSubject(notification.getSubject()); setText(notification.getText()); + setAdditionalConfig(notification.getAdditionalConfig()); setInfo(toJson(notification.getInfo())); setStatus(notification.getStatus()); } @@ -94,6 +99,7 @@ public class NotificationEntity extends BaseSqlEntity { notification.setType(type); notification.setSubject(subject); notification.setText(text); + notification.setAdditionalConfig(additionalConfig); notification.setInfo(fromJson(info, NotificationInfo.class)); notification.setStatus(status); return notification; diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java index a7388e7988..5a6d1e58b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationRuleService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheKey; import org.thingsboard.server.dao.notification.cache.NotificationRuleCacheValue; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -49,6 +50,9 @@ public class DefaultNotificationRuleService extends AbstractCachedEntityService< publishEvictEvent(notificationRule); } catch (Exception e) { handleEvictEvent(notificationRule); + checkConstraintViolation(e, Map.of( + "uq_notification_rule_name", "Notification rule with such name already exists" + )); throw e; } return notificationRule; diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java index 0a073fb653..3a15c1e6ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationService.java @@ -47,29 +47,34 @@ public class DefaultNotificationService implements NotificationService { } @Override - public boolean markNotificationAsRead(TenantId tenantId, UserId userId, NotificationId notificationId) { - return notificationDao.updateStatusByIdAndUserId(tenantId, userId, notificationId, NotificationStatus.READ); + public boolean markNotificationAsRead(TenantId tenantId, UserId recipientId, NotificationId notificationId) { + return notificationDao.updateStatusByIdAndRecipientId(tenantId, recipientId, notificationId, NotificationStatus.READ); } @Override - public PageData findNotificationsByUserIdAndReadStatus(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink) { + public int markAllNotificationsAsRead(TenantId tenantId, UserId recipientId) { + return notificationDao.updateStatusByRecipientId(tenantId, recipientId, NotificationStatus.READ); + } + + @Override + public PageData findNotificationsByRecipientIdAndReadStatus(TenantId tenantId, UserId recipientId, boolean unreadOnly, PageLink pageLink) { if (unreadOnly) { - return notificationDao.findUnreadByUserIdAndPageLink(tenantId, userId, pageLink); + return notificationDao.findUnreadByRecipientIdAndPageLink(tenantId, recipientId, pageLink); } else { - return notificationDao.findByUserIdAndPageLink(tenantId, userId, pageLink); + return notificationDao.findByRecipientIdAndPageLink(tenantId, recipientId, pageLink); } } @Override - public PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit) { + public PageData findLatestUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId, int limit) { SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); PageLink pageLink = new PageLink(limit, 0, null, sortOrder); - return findNotificationsByUserIdAndReadStatus(tenantId, userId, true, pageLink); + return findNotificationsByRecipientIdAndReadStatus(tenantId, recipientId, true, pageLink); } @Override - public int countUnreadNotificationsByUserId(TenantId tenantId, UserId userId) { - return notificationDao.countUnreadByUserId(tenantId, userId); + public int countUnreadNotificationsByRecipientId(TenantId tenantId, UserId recipientId) { + return notificationDao.countUnreadByRecipientId(tenantId, recipientId); } @Override @@ -78,8 +83,8 @@ public class DefaultNotificationService implements NotificationService { } @Override - public boolean deleteNotification(TenantId tenantId, UserId userId, NotificationId notificationId) { - return notificationDao.deleteByIdAndUserId(tenantId, userId, notificationId); + public boolean deleteNotification(TenantId tenantId, UserId recipientId, NotificationId notificationId) { + return notificationDao.deleteByIdAndRecipientId(tenantId, recipientId, notificationId); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java index 83ea270a28..dda4145a31 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTargetService.java @@ -30,16 +30,18 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge import org.thingsboard.server.common.data.notification.targets.UserListNotificationTargetConfig; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.user.UserService; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @Service @Slf4j @RequiredArgsConstructor -public class DefaultNotificationTargetService implements NotificationTargetService { +public class DefaultNotificationTargetService extends AbstractEntityService implements NotificationTargetService { private final NotificationTargetDao notificationTargetDao; private final NotificationRequestDao notificationRequestDao; @@ -48,7 +50,14 @@ public class DefaultNotificationTargetService implements NotificationTargetServi @Override public NotificationTarget saveNotificationTarget(TenantId tenantId, NotificationTarget notificationTarget) { - return notificationTargetDao.save(tenantId, notificationTarget); + try { + return notificationTargetDao.saveAndFlush(tenantId, notificationTarget); + } catch (Exception e) { + checkConstraintViolation(e, Map.of( + "uq_notification_target_name", "Notification target with such name already exists" + )); + throw e; + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java index 82e21d1388..6983b112b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotificationTemplateService.java @@ -43,7 +43,14 @@ public class DefaultNotificationTemplateService extends AbstractEntityService im @Override public NotificationTemplate saveNotificationTemplate(TenantId tenantId, NotificationTemplate notificationTemplate) { - return notificationTemplateDao.save(tenantId, notificationTemplate); + try { + return notificationTemplateDao.saveAndFlush(tenantId, notificationTemplate); + } catch (Exception e) { + checkConstraintViolation(e, Map.of( + "uq_notification_template_name", "Notification template with such name already exists" + )); + throw e; + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java index 910417017d..8d1bbc53d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationDao.java @@ -27,18 +27,20 @@ import org.thingsboard.server.dao.Dao; public interface NotificationDao extends Dao { - PageData findUnreadByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + PageData findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink); - PageData findByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + PageData findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink); - boolean updateStatusByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status); + boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status); - int countUnreadByUserId(TenantId tenantId, UserId userId); + int countUnreadByRecipientId(TenantId tenantId, UserId recipientId); PageData findByRequestId(TenantId tenantId, NotificationRequestId notificationRequestId, PageLink pageLink); void updateStatusesByRequestId(TenantId tenantId, NotificationRequestId requestId, NotificationStatus status); - boolean deleteByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId); + boolean deleteByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId); + + int updateStatusByRecipientId(TenantId tenantId, UserId recipientId, NotificationStatus status); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java index 43a87d1cbd..62eb46e8a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationDao.java @@ -64,23 +64,23 @@ public class JpaNotificationDao extends JpaAbstractDao findUnreadByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { - return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(userId.getId(), NotificationStatus.READ, DaoUtil.toPageable(pageLink))); + public PageData findUnreadByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ, DaoUtil.toPageable(pageLink))); } @Override - public PageData findByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { - return DaoUtil.toPageData(notificationRepository.findByRecipientId(userId.getId(), DaoUtil.toPageable(pageLink))); + public PageData findByRecipientIdAndPageLink(TenantId tenantId, UserId recipientId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByRecipientId(recipientId.getId(), DaoUtil.toPageable(pageLink))); } @Override - public boolean updateStatusByIdAndUserId(TenantId tenantId, UserId userId, NotificationId notificationId, NotificationStatus status) { - return notificationRepository.updateStatusByIdAndRecipientId(notificationId.getId(), userId.getId(), status) != 0; + public boolean updateStatusByIdAndRecipientId(TenantId tenantId, UserId recipientId, NotificationId notificationId, NotificationStatus status) { + return notificationRepository.updateStatusByIdAndRecipientId(notificationId.getId(), recipientId.getId(), status) != 0; } @Override - public int countUnreadByUserId(TenantId tenantId, UserId userId) { - return notificationRepository.countByRecipientIdAndStatusNot(userId.getId(), NotificationStatus.READ); + public int countUnreadByRecipientId(TenantId tenantId, UserId recipientId) { + return notificationRepository.countByRecipientIdAndStatusNot(recipientId.getId(), NotificationStatus.READ); } @Override @@ -94,8 +94,13 @@ public class JpaNotificationDao extends JpaAbstractDao