diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java new file mode 100644 index 0000000000..454a8422e5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationController.java @@ -0,0 +1,104 @@ +/** + * 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.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.notification.NotificationService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.notification.NotificationProcessingService; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.util.UUID; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class NotificationController extends BaseController { + + private final NotificationService notificationService; + private final NotificationProcessingService notificationProcessingService; + + @GetMapping("/notifications") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public PageData getNotifications(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(defaultValue = "false") boolean unreadOnly, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationService.findNotificationsByUserIdAndReadStatusAndPageLink(user.getTenantId(), user.getId(), unreadOnly, pageLink); + } + + @PutMapping("/notification/{id}/read") // or maybe to NotificationUpdateRequest for the future + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public void markNotificationAsRead(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationId notificationId = new NotificationId(id); + notificationService.updateNotificationStatus(user.getTenantId(), notificationId, NotificationStatus.READ); + } + + + @PostMapping("/notification/request") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationRequest createNotificationRequest(@RequestBody NotificationRequest notificationRequest, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); + return notificationProcessingService.processNotificationRequest(user, notificationRequest); + } + + @GetMapping("/notification/requests") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationRequests(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + accessControlService.checkPermission(user, Resource.NOTIFICATION, Operation.CREATE); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationService.findNotificationRequestsByTenantIdAndPageLink(user.getTenantId(), pageLink); + } + + // delete request and sent notifications + public void deleteNotificationRequest() { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java index 220b4f2be2..471b72e015 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTargetController.java @@ -19,16 +19,32 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.notification.NotificationTargetService; +import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; @RestController @TbCoreComponent @@ -36,21 +52,58 @@ import org.thingsboard.server.service.security.model.SecurityUser; @RequiredArgsConstructor @Slf4j public class NotificationTargetController extends BaseController { + // fixme: permission check, log action, events private final NotificationTargetService notificationTargetService; + private final UserService userService; @PostMapping("/target") - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public NotificationTarget saveNotificationTarget(NotificationTarget notificationTarget) throws Exception { - SecurityUser user = getCurrentUser(); - // fixme: permission check, log action + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTarget saveNotificationTarget(@RequestBody NotificationTarget notificationTarget, + @AuthenticationPrincipal SecurityUser user) { return notificationTargetService.saveNotificationTarget(user.getTenantId(), notificationTarget); } + @GetMapping("/target/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public NotificationTarget getNotificationTargetById(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + return notificationTargetService.findNotificationTargetById(user.getTenantId(), notificationTargetId); + } + + @GetMapping("/target/{id}/recipients") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public List getRecipientsForNotificationTarget(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + // fixme: to page data + // todo: check read permission for recipients + List recipients = new ArrayList<>(); + for (UserId userId : notificationTargetService.findRecipientsForNotificationTarget(user.getTenantId(), notificationTargetId)) { + recipients.add(checkUserId(userId, Operation.READ)); + } + return recipients; + } + @GetMapping("/targets") - public PageData getNotificationTargets(@AuthenticationPrincipal SecurityUser user) throws ThingsboardException { -// notificationTargetService.findNotificationTargetsByTenantIdAndPageLink() - return null; + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public PageData getNotificationTargets(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @AuthenticationPrincipal SecurityUser user) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return notificationTargetService.findNotificationTargetsByTenantIdAndPageLink(user.getTenantId(), pageLink); + } + + @DeleteMapping("/target/{id}") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + public void deleteNotificationTarget(@PathVariable UUID id, + @AuthenticationPrincipal SecurityUser user) { + NotificationTargetId notificationTargetId = new NotificationTargetId(id); + notificationTargetService.deleteNotificationTarget(user.getTenantId(), notificationTargetId); } } diff --git a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java index 3aa74862a6..04bc45b9d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationProcessingService.java @@ -17,11 +17,15 @@ package org.thingsboard.server.service.notification; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.notification.NotificationService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.user.UserService; @@ -29,9 +33,13 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.telemetry.AbstractSubscriptionService; +import java.util.ArrayList; import java.util.List; +import java.util.Map; @Service @Slf4j @@ -57,37 +65,57 @@ public class DefaultNotificationProcessingService extends AbstractSubscriptionSe } @Override - public void processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) { + public NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException { TenantId tenantId = user.getTenantId(); - notificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); - - List recipients = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); - for (UserId recipientId : recipients) { - try { - // todo: check read permission for recipientId - Notification notification = Notification.builder() - .tenantId(tenantId) - .requestId(notificationRequest.getId()) - .status(null) - .recipientId(recipientId) - .text(formatNotificationText(notificationRequest.getTextTemplate(), null)) - .severity(notificationRequest.getSeverity()) - .senderId(notificationRequest.getSenderId()) - .build(); - notification = notificationService.createNotification(tenantId, notification); + List recipientsIds = notificationTargetService.findRecipientsForNotificationTarget(tenantId, notificationRequest.getTargetId()); + List recipients = new ArrayList<>(); + for (UserId recipientId : recipientsIds) { + User recipient = userService.findUserById(tenantId, recipientId); // todo: add caching + accessControlService.checkPermission(user, Resource.USER, Operation.READ, recipientId, recipient); + recipients.add(recipient); + } + + notificationRequest.setTenantId(tenantId); + notificationRequest.setSenderId(user.getId()); + NotificationRequest savedNotificationRequest = notificationService.createNotificationRequest(tenantId, notificationRequest); + + // todo: delayed sending; check all delayed notification requests on start up, schedule send + + for (User recipient : recipients) { + dbCallbackExecutorService.submit(() -> { + Notification notification = createNotification(recipient, notificationRequest); onNewNotification(notification); - } catch (Exception e) { - // fixme: handle - } + }); } + return savedNotificationRequest; + } + + private Notification createNotification(User recipient, NotificationRequest notificationRequest) { + Notification notification = Notification.builder() + .requestId(notificationRequest.getId()) + .recipientId(recipient.getId()) + .text(formatNotificationText(notificationRequest.getTextTemplate(), recipient)) + .severity(notificationRequest.getNotificationSeverity()) + .status(NotificationStatus.SENT) + .build(); + notification = notificationService.createNotification(recipient.getTenantId(), notification); + return notification; } private void onNewNotification(Notification notification) { + wsCallBackExecutor.submit(() -> { + + }) } - private String formatNotificationText(String template, Object context) { - return template; + private String formatNotificationText(String template, User recipient) { + Map context = Map.of( + "recipientEmail", recipient.getEmail(), + "recipientFirstName", recipient.getFirstName(), + "recipientLastName", recipient.getLastName() + ); + return TbNodeUtils.processTemplate(template, context); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java index d094c5e4b4..1293ba999d 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/NotificationProcessingService.java @@ -15,11 +15,12 @@ */ package org.thingsboard.server.service.notification; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.notification.NotificationRequest; import org.thingsboard.server.service.security.model.SecurityUser; public interface NotificationProcessingService { - void processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest); + NotificationRequest processNotificationRequest(SecurityUser user, NotificationRequest notificationRequest) throws ThingsboardException; } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f5ac40afd5..87e5487a7c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -267,6 +267,8 @@ sql: stats_print_interval_ms: "${SQL_EDGE_EVENTS_BATCH_STATS_PRINT_MS:10000}" audit_logs: partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week + notifications: + partition_size: "${SQL_NOTIFICATIONS_PARTITION_SIZE_HOURS:168}" # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks batch_sort: "${SQL_BATCH_SORT:false}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert 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 3dfca4ae36..a582758639 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,10 +30,13 @@ public interface NotificationService { PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + Notification createNotification(TenantId tenantId, Notification notification); void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); - PageData findNotificationsByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink); + + PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java index 52b757191b..f6cdf780e5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetService.java @@ -34,4 +34,6 @@ public interface NotificationTargetService { List findRecipientsForNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); + void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId); + } 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 3e9c88d45e..02f0fb2aa4 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 @@ -23,12 +23,8 @@ import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.NotificationRequestId; -import org.thingsboard.server.common.data.id.NotificationTargetId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import java.util.UUID; - @Data @AllArgsConstructor @NoArgsConstructor @@ -37,12 +33,11 @@ import java.util.UUID; public class Notification extends SearchTextBased { private NotificationRequestId requestId; - private TenantId tenantId; private UserId recipientId; private String text; private NotificationSeverity severity; private NotificationStatus status; - private UserId senderId; +// private UserId senderId; @Override public String getSearchText() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java new file mode 100644 index 0000000000..4831e056cd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationInfo.java @@ -0,0 +1,37 @@ +/** + * 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.common.data.notification; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Data; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.validation.NoXss; + + +@Data +//@JsonIgnoreProperties(ignoreUnknown = true) +//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "notificationType", visible = true, defaultImpl = NotificationInfo.class) +//@JsonSubTypes({ +// @Type(name = "ALARM", value = DeviceExportData.class), +//}) +public class NotificationInfo { + @NoXss + private String description; + + private ObjectNode alarmDetails; // move to child class + private DashboardId dashboardId; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java index 3b3b2c527e..392063ad93 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequest.java @@ -15,39 +15,45 @@ */ package org.thingsboard.server.common.data.notification; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.SearchTextBased; -import org.thingsboard.server.common.data.id.NotificationId; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.validation.NoXss; -import java.util.UUID; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = true) -public class NotificationRequest extends SearchTextBased { +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class NotificationRequest extends BaseData implements HasTenantId { - private NotificationTargetId targetId; - private String textTemplate; // html with params? - private Object notificationType; // ALARM, ADMIN, - private Object notificationInfo; // for alarms: alarm details, link to dashboard etc. - private NotificationSeverity severity; private TenantId tenantId; + @NotNull(message = "Target is not specified") + private NotificationTargetId targetId; + @NoXss + private String notificationReason; // "Alarm", "Scheduled event". "General" by default + // @NoXss + @NotBlank(message = "Notification text template is missing") + private String textTemplate; + @Valid + private NotificationInfo notificationInfo; + private NotificationSeverity notificationSeverity; + private NotificationRequestConfig additionalConfig; private UserId senderId; - @Override - public String getSearchText() { - return textTemplate; - } - - // todo: scheduling + public static final String GENERAL_NOTIFICATION_REASON = "General"; + public static final String ALARM_NOTIFICATION_REASON = "Alarm"; } - -/* -* NotificationService - manages NotificationRequest and Notification entities -* NotificationTargetService - manages NotificationTarget -* */ \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java new file mode 100644 index 0000000000..8f65427f21 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationRequestConfig.java @@ -0,0 +1,23 @@ +/** + * 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.common.data.notification; + +import lombok.Data; + +@Data +public class NotificationRequestConfig { + private Long sendingDelayMs; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java index 14008c34bb..7e504cbe15 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationStatus.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.notification; public enum NotificationStatus { + SENT, DELIVERED, READ } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java index 0048f24be5..957a6d359f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTarget.java @@ -17,23 +17,18 @@ package org.thingsboard.server.common.data.notification.targets; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.NotificationTargetId; import org.thingsboard.server.common.data.id.TenantId; @Data @EqualsAndHashCode(callSuper = true) -public class NotificationTarget extends SearchTextBased implements HasTenantId, HasName { +public class NotificationTarget extends BaseData implements HasTenantId, HasName { private TenantId tenantId; private String name; private NotificationTargetConfig configuration; - @Override - public String getSearchText() { - return name; - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java index 366f78ef6e..0623bf07b9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfig.java @@ -23,7 +23,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ - @Type(value = SingleUserNotificationTargetConfig.class, name = "SINGLE_USER") + @Type(value = SingleUserNotificationTargetConfig.class, name = "SINGLE_USER"), + @Type(value = UserListNotificationTargetConfig.class, name = "USER_LIST") }) public interface NotificationTargetConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java index 8bc6d04c56..2413eb9326 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetConfigType.java @@ -17,7 +17,9 @@ package org.thingsboard.server.common.data.notification.targets; public enum NotificationTargetConfigType { SINGLE_USER, - USER_GROUP, - USERS_WITH_ROLE, - QUERY // ? + USER_LIST, + +// USER_GROUP, +// USERS_WITH_ROLE, +// QUERY // ? } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java new file mode 100644 index 0000000000..22bc26703d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/UserListNotificationTargetConfig.java @@ -0,0 +1,33 @@ +/** + * 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.common.data.notification.targets; + +import lombok.Data; +import org.thingsboard.server.common.data.id.UserId; + +import java.util.List; + +@Data +public class UserListNotificationTargetConfig implements NotificationTargetConfig { + + private List usersIds; + + @Override + public NotificationTargetConfigType getType() { + return NotificationTargetConfigType.USER_LIST; + } + +} 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 4ebcaaf873..5c938de98c 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 @@ -652,6 +652,21 @@ public class ModelConstants { public static final String NOTIFICATION_TARGET_TABLE_NAME = "notification_target"; public static final String NOTIFICATION_TARGET_CONFIGURATION_PROPERTY = "configuration"; + public static final String NOTIFICATION_TABLE_NAME = "notification"; + public static final String NOTIFICATION_REQUEST_ID_PROPERTY = "request_id"; + public static final String NOTIFICATION_RECIPIENT_ID_PROPERTY = "recipient_id"; + public static final String NOTIFICATION_TEXT_PROPERTY = "text"; + public static final String NOTIFICATION_SEVERITY_PROPERTY = "severity"; + public static final String NOTIFICATION_STATUS_PROPERTY = "status"; + + public static final String NOTIFICATION_REQUEST_TABLE_NAME = "notification_request"; + public static final String NOTIFICATION_REQUEST_TARGET_ID_PROPERTY = "target_id"; + public static final String NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY = "text_template"; + public static final String NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY = "notification_reason"; + public static final String NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY = "notification_info"; + public static final String NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY = "notification_severity"; + public static final String NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY = "additional_config"; + public static final String NOTIFICATION_REQUEST_SENDER_ID_PROPERTY = "sender_id"; protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; 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 782600c742..ad7c506d4b 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 @@ -15,15 +15,67 @@ */ package org.thingsboard.server.dao.model.sql; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationSeverity; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; +@Data +@EqualsAndHashCode(callSuper = true) @Entity +@Table(name = ModelConstants.NOTIFICATION_TABLE_NAME) public class NotificationEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_ID_PROPERTY) + private UUID requestId; + + @Column(name = ModelConstants.NOTIFICATION_RECIPIENT_ID_PROPERTY) + private UUID recipientId; + + @Column(name = ModelConstants.NOTIFICATION_TEXT_PROPERTY) + private String text; + + @Column(name = ModelConstants.NOTIFICATION_SEVERITY_PROPERTY) + private NotificationSeverity severity; + + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_STATUS_PROPERTY) + private NotificationStatus status; + + public NotificationEntity() {} + + public NotificationEntity(Notification notification) { + setId(notification.getUuidId()); + setCreatedTime(notification.getCreatedTime()); + setRequestId(getUuid(notification.getRequestId())); + setRecipientId(getUuid(notification.getRecipientId())); + setText(notification.getText()); + setStatus(notification.getStatus()); + } + @Override public Notification toData() { - return null; + Notification notification = new Notification(); + notification.setId(new NotificationId(id)); + notification.setCreatedTime(createdTime); + notification.setRequestId(createId(requestId, NotificationRequestId::new)); + notification.setRecipientId(createId(recipientId, UserId::new)); + notification.setText(text); + notification.setStatus(status); + return notification; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java index 6ba482dd5e..23a8ae1059 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationRequestEntity.java @@ -15,15 +15,94 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.NotificationRequestId; +import org.thingsboard.server.common.data.id.NotificationTargetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.notification.NotificationInfo; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationRequestConfig; +import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; +import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; +@Data +@EqualsAndHashCode(callSuper = true) @Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.NOTIFICATION_REQUEST_TABLE_NAME) public class NotificationRequestEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.TENANT_ID_PROPERTY) + private UUID tenantId; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_TARGET_ID_PROPERTY) + private UUID targetId; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_REASON_PROPERTY) + private String notificationReason; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_TEXT_TEMPLATE_PROPERTY) + private String textTemplate; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_INFO_PROPERTY) + private JsonNode notificationInfo; + + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.NOTIFICATION_REQUEST_NOTIFICATION_SEVERITY_PROPERTY) + private NotificationSeverity notificationSeverity; + + @Type(type = "json") + @Column(name = ModelConstants.NOTIFICATION_REQUEST_ADDITIONAL_CONFIG_PROPERTY) + private JsonNode additionalConfig; + + @Column(name = ModelConstants.NOTIFICATION_REQUEST_SENDER_ID_PROPERTY) + private UUID senderId; + + public NotificationRequestEntity() {} + + public NotificationRequestEntity(NotificationRequest notificationRequest) { + setId(notificationRequest.getUuidId()); + setCreatedTime(notificationRequest.getCreatedTime()); + setTenantId(getUuid(notificationRequest.getTenantId())); + setTargetId(getUuid(notificationRequest.getTargetId())); + setNotificationReason(notificationRequest.getNotificationReason()); + setTextTemplate(notificationRequest.getTextTemplate()); + setNotificationInfo(JacksonUtil.valueToTree(notificationRequest.getNotificationInfo())); + setNotificationSeverity(notificationRequest.getNotificationSeverity()); + setAdditionalConfig(JacksonUtil.valueToTree(notificationRequest.getAdditionalConfig())); + setSenderId(getUuid(notificationRequest.getSenderId())); + } + @Override public NotificationRequest toData() { - return null; + NotificationRequest notificationRequest = new NotificationRequest(); + notificationRequest.setId(new NotificationRequestId(id)); + notificationRequest.setCreatedTime(createdTime); + notificationRequest.setTenantId(createId(tenantId, TenantId::new)); + notificationRequest.setTargetId(createId(targetId, NotificationTargetId::new)); + notificationRequest.setNotificationReason(notificationReason); + notificationRequest.setTextTemplate(textTemplate); + notificationRequest.setNotificationInfo(JacksonUtil.treeToValue(notificationInfo, NotificationInfo.class)); + notificationRequest.setNotificationSeverity(notificationSeverity); + notificationRequest.setAdditionalConfig(JacksonUtil.treeToValue(additionalConfig, NotificationRequestConfig.class)); + notificationRequest.setSenderId(createId(senderId, UserId::new)); + return notificationRequest; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java index 6cbb8c93c9..70d51833a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/NotificationTargetEntity.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.notification.targets.NotificationTarge import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; 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 23e7149866..8b1b994b17 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 @@ -17,16 +17,21 @@ package org.thingsboard.server.dao.notification; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.NotificationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.notification.NotificationSeverity; import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.sql.query.EntityKeyMapping; @Service @Slf4j @@ -35,13 +40,16 @@ public class DefaultNotificationService implements NotificationService { private final NotificationRequestDao notificationRequestDao; private final NotificationDao notificationDao; - private final NotificationTargetService notificationTargetService; + private final NotificationRequestValidator notificationRequestValidator = new NotificationRequestValidator(); @Override public NotificationRequest createNotificationRequest(TenantId tenantId, NotificationRequest notificationRequest) { - if (notificationRequest.getId() != null) { - throw new IllegalArgumentException(); + if (StringUtils.isBlank(notificationRequest.getNotificationReason())) { + notificationRequest.setNotificationReason(NotificationRequest.GENERAL_NOTIFICATION_REASON); + } + if (notificationRequest.getNotificationSeverity() == null) { + notificationRequest.setNotificationSeverity(NotificationSeverity.NORMAL); } notificationRequestValidator.validate(notificationRequest, NotificationRequest::getTenantId); return notificationRequestDao.save(tenantId, notificationRequest); @@ -49,28 +57,52 @@ public class DefaultNotificationService implements NotificationService { @Override public PageData findNotificationRequestsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { - return null; + return notificationRequestDao.findByTenantIdAndPageLink(tenantId, pageLink); } @Override public Notification createNotification(TenantId tenantId, Notification notification) { if (notification.getId() != null) { - throw new IllegalArgumentException(); + throw new DataValidationException("Notification cannot be updated"); // tmp ? } return notificationDao.save(tenantId, notification); } @Override public void updateNotificationStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { + notificationDao.updateStatus(tenantId, notificationId, status); + } + @Override + public PageData findNotificationsByUserIdAndReadStatusAndPageLink(TenantId tenantId, UserId userId, boolean unreadOnly, PageLink pageLink) { + if (unreadOnly) { + return notificationDao.findUnreadByUserIdAndPageLink(tenantId, userId, pageLink); + } else { + return notificationDao.findByUserIdAndPageLink(tenantId, userId, pageLink); + } } @Override - public PageData findNotificationsByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { - return null; + public PageData findLatestUnreadNotificationsByUserId(TenantId tenantId, UserId userId, int limit) { + SortOrder sortOrder = new SortOrder(EntityKeyMapping.CREATED_TIME, SortOrder.Direction.DESC); + PageLink pageLink = new PageLink(limit, 0, null, sortOrder); + return findNotificationsByUserIdAndReadStatusAndPageLink(tenantId, userId, true, pageLink); } private static class NotificationRequestValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, NotificationRequest notificationRequest) { + if (notificationRequest.getId() != null) { + throw new DataValidationException("Notification request cannot be changed once created"); + } + if (notificationRequest.getSenderId() != null) { + if (notificationRequest.getNotificationReason().equalsIgnoreCase(NotificationRequest.ALARM_NOTIFICATION_REASON)) { + throw new DataValidationException("'Alarm' notification reason is for internal usage"); + } + } + } + } } 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 6e8548d233..a7d5393d43 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 @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.SingleUserNotificationTargetConfig; +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.service.DataValidator; @@ -47,12 +48,12 @@ public class DefaultNotificationTargetService implements NotificationTargetServi @Override public NotificationTarget findNotificationTargetById(TenantId tenantId, NotificationTargetId id) { - return null; + return notificationTargetDao.findById(tenantId, id.getId()); } @Override public PageData findNotificationTargetsByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { - return null; + return notificationTargetDao.findByTenantIdAndPageLink(tenantId, pageLink); } @Override @@ -65,10 +66,20 @@ public class DefaultNotificationTargetService implements NotificationTargetServi SingleUserNotificationTargetConfig singleUserNotificationTargetConfig = (SingleUserNotificationTargetConfig) configuration; recipients.add(singleUserNotificationTargetConfig.getUserId()); break; + case USER_LIST: + UserListNotificationTargetConfig userListNotificationTargetConfig = (UserListNotificationTargetConfig) configuration; + recipients.addAll(userListNotificationTargetConfig.getUsersIds()); + break; } return recipients; } + @Override + public void deleteNotificationTarget(TenantId tenantId, NotificationTargetId notificationTargetId) { + notificationTargetDao.removeById(tenantId, notificationTargetId.getId()); + // todo: delete related notification requests (?) + } + private static class NotificationTargetValidator extends DataValidator { } 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 f7d8013f3e..b088d75c35 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 @@ -15,8 +15,21 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationDao extends Dao { + + PageData findUnreadByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + + PageData findByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink); + + void updateStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java index c93ca57beb..d518a4e245 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationRequestDao.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationRequestDao extends Dao { + + PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java index 07101f60ee..0bcae5c456 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/NotificationTargetDao.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.dao.notification; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; public interface NotificationTargetDao extends Dao { + + PageData findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); + } 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 909456e765..bb9c30a71b 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 @@ -15,16 +15,28 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.NotificationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.notification.Notification; +import org.thingsboard.server.common.data.notification.NotificationStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sql.NotificationEntity; import org.thingsboard.server.dao.notification.NotificationDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.util.SqlDao; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Component @SqlDao @@ -32,6 +44,38 @@ import java.util.UUID; public class JpaNotificationDao extends JpaAbstractDao implements NotificationDao { private final NotificationRepository notificationRepository; + private final SqlPartitioningRepository partitioningRepository; + + @Value("${sql.notifications.partition_size:168}") + private int partitionSizeInHours; + + @Override + public Notification save(TenantId tenantId, Notification notification) { + if (notification.getId() == null) { + UUID uuid = Uuids.timeBased(); + notification.setId(new NotificationId(uuid)); + notification.setCreatedTime(Uuids.unixTimestamp(uuid)); + // todo: regarding ttl, it might be better to remove notifications on NotificationRequest level + partitioningRepository.createPartitionIfNotExists(ModelConstants.NOTIFICATION_TABLE_NAME, + notification.getCreatedTime(), TimeUnit.HOURS.toMillis(partitionSizeInHours)); + } + return super.save(tenantId, notification); + } + + @Override + public PageData findUnreadByUserIdAndPageLink(TenantId tenantId, UserId userId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRepository.findByRecipientIdAndStatusNot(userId.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))); + } + + @Override + public void updateStatus(TenantId tenantId, NotificationId notificationId, NotificationStatus status) { + notificationRepository.updateStatus(notificationId.getId(), status); + } @Override protected Class getEntityClass() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java index 6b6119b871..099218abcc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationRequestDao.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationRequest; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; import org.thingsboard.server.dao.notification.NotificationRequestDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; @@ -33,6 +38,12 @@ public class JpaNotificationRequestDao extends JpaAbstractDao findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(notificationRequestRepository.findByTenantIdAndSearchText(tenantId.getId(), + Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return NotificationRequestEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java index e22f342272..26b9bfed76 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/JpaNotificationTargetDao.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.dao.sql.notification; +import com.google.common.base.Strings; import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.NotificationTarget; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; import org.thingsboard.server.dao.notification.NotificationTargetDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; @@ -33,6 +38,12 @@ public class JpaNotificationTargetDao extends JpaAbstractDao findByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(notificationTargetRepository.findByTenantIdAndNameContainingIgnoreCase(tenantId.getId(), + Strings.nullToEmpty(pageLink.getTextSearch()), DaoUtil.toPageable(pageLink))); + } + @Override protected Class getEntityClass() { return NotificationTargetEntity.class; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java index db8727f167..9ee5b07121 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRepository.java @@ -15,12 +15,29 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.notification.NotificationStatus; import org.thingsboard.server.dao.model.sql.NotificationEntity; import java.util.UUID; @Repository public interface NotificationRepository extends JpaRepository { + + Page findByRecipientIdAndStatusNot(UUID recipientId, NotificationStatus status, Pageable pageable); + + Page findByRecipientId(UUID recipientId, Pageable pageable); + + @Modifying + @Transactional + @Query("UPDATE NotificationEntity n SET n.status = :status WHERE n.id = :id") + void updateStatus(@Param("id") UUID id, @Param("status") NotificationStatus status); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java index 370e434c14..1303c99034 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationRequestRepository.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.NotificationRequestEntity; @@ -23,4 +27,11 @@ import java.util.UUID; @Repository public interface NotificationRequestRepository extends JpaRepository { + + @Query("SELECT r FROM NotificationRequestEntity r WHERE r.tenantId = :tenantId AND " + + "(lower(r.notificationReason) LIKE lower(concat('%', :searchText, '%')) OR " + + "lower(r.textTemplate) LIKE lower(concat('%', :searchText, '%')))") + Page findByTenantIdAndSearchText(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java index e3a4260102..a558ad5163 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/notification/NotificationTargetRepository.java @@ -15,7 +15,11 @@ */ package org.thingsboard.server.dao.sql.notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.thingsboard.server.dao.model.sql.NotificationTargetEntity; @@ -23,4 +27,7 @@ import java.util.UUID; @Repository public interface NotificationTargetRepository extends JpaRepository { + + Page findByTenantIdAndNameContainingIgnoreCase(UUID tenantId, String searchText, Pageable pageable); + }