diff --git a/src/main/java/com/chat/yourway/controller/ChatController.java b/src/main/java/com/chat/yourway/controller/ChatController.java index fa9d0385..278e5b6e 100644 --- a/src/main/java/com/chat/yourway/controller/ChatController.java +++ b/src/main/java/com/chat/yourway/controller/ChatController.java @@ -40,5 +40,4 @@ public List getTopicHistory(@DestinationVariable Integer top String email = principal.getName(); return chatMessageService.sendMessageHistoryByTopicId(topicId, pageRequestDto, email); } - } diff --git a/src/main/java/com/chat/yourway/dto/response/MessageNotificationResponseDto.java b/src/main/java/com/chat/yourway/dto/response/MessageNotificationResponseDto.java index baf05554..3fd21933 100644 --- a/src/main/java/com/chat/yourway/dto/response/MessageNotificationResponseDto.java +++ b/src/main/java/com/chat/yourway/dto/response/MessageNotificationResponseDto.java @@ -15,11 +15,9 @@ @ToString public class MessageNotificationResponseDto { - private String email; private Integer topicId; + private String email; private EventType status; - private Integer unreadMessages; private LocalDateTime lastRead; - private LastMessageResponseDto lastMessage; } diff --git a/src/main/java/com/chat/yourway/dto/response/TopicNotificationResponseDto.java b/src/main/java/com/chat/yourway/dto/response/TopicNotificationResponseDto.java new file mode 100644 index 00000000..5de5165d --- /dev/null +++ b/src/main/java/com/chat/yourway/dto/response/TopicNotificationResponseDto.java @@ -0,0 +1,23 @@ +package com.chat.yourway.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +@ToString +public class TopicNotificationResponseDto { + + private TopicResponseDto topic; + + private Integer unreadMessages; + + private LastMessageResponseDto lastMessage; + + +} diff --git a/src/main/java/com/chat/yourway/listener/StompSubscriptionListener.java b/src/main/java/com/chat/yourway/listener/StompSubscriptionListener.java index 9d205b91..2fae796c 100644 --- a/src/main/java/com/chat/yourway/listener/StompSubscriptionListener.java +++ b/src/main/java/com/chat/yourway/listener/StompSubscriptionListener.java @@ -32,6 +32,7 @@ public class StompSubscriptionListener { private static LastMessageResponseDto lastMessageDto; private static final String USER_DESTINATION = "/user"; + private static final String TOPICS_DESTINATION = "/topics"; private static final String SLASH = "/"; @EventListener @@ -49,11 +50,16 @@ public void handleWebSocketSubscribeListener(SessionSubscribeEvent event) { } chatNotificationService.notifyTopicSubscribers(getTopicId(event)); + chatNotificationService.notifyAllWhoSubscribedToTopic(getTopicId(event)); } catch (NumberFormatException e) { log.warn("Contact [{}] subscribe to destination [{}] without topic id", email, destination); } + if (destination.equals(USER_DESTINATION + properties.getNotifyPrefix() + TOPICS_DESTINATION)) { + chatNotificationService.notifyAllPublicTopics(getEmail(event)); + } + log.info("Contact [{}] subscribe to [{}]", email, destination); } diff --git a/src/main/java/com/chat/yourway/mapper/MessageNotificationMapper.java b/src/main/java/com/chat/yourway/mapper/MessageNotificationMapper.java index 3dd01734..7a80c96e 100644 --- a/src/main/java/com/chat/yourway/mapper/MessageNotificationMapper.java +++ b/src/main/java/com/chat/yourway/mapper/MessageNotificationMapper.java @@ -8,7 +8,6 @@ @Mapper(componentModel = "spring") public interface MessageNotificationMapper { - @Mapping(target = "unreadMessages", ignore = true, defaultValue = "0") @Mapping(target = "status", source = "eventType") @Mapping(target = "lastRead", source = "timestamp") @Mapping(target = "email", source = "email") diff --git a/src/main/java/com/chat/yourway/service/ChatMessageServiceImpl.java b/src/main/java/com/chat/yourway/service/ChatMessageServiceImpl.java index 26c1b9d9..8191aee0 100644 --- a/src/main/java/com/chat/yourway/service/ChatMessageServiceImpl.java +++ b/src/main/java/com/chat/yourway/service/ChatMessageServiceImpl.java @@ -74,8 +74,11 @@ private void sendToTopic(Integer topicId, MessageResponseDto messageDto) { lastMessageDto.setLastMessage(messageDto.getContent()); contactEventService.setLastMessageToAllTopicSubscribers(topicId, lastMessageDto); + simpMessagingTemplate.convertAndSend(toTopicDestination(topicId), messageDto); + chatNotificationService.notifyTopicSubscribers(topicId); + chatNotificationService.notifyAllWhoSubscribedToTopic(topicId); } private String toTopicDestination(Integer topicId) { diff --git a/src/main/java/com/chat/yourway/service/ChatNotificationServiceImpl.java b/src/main/java/com/chat/yourway/service/ChatNotificationServiceImpl.java index 7f3fe517..a3b5772b 100644 --- a/src/main/java/com/chat/yourway/service/ChatNotificationServiceImpl.java +++ b/src/main/java/com/chat/yourway/service/ChatNotificationServiceImpl.java @@ -25,7 +25,7 @@ public void notifyTopicSubscribers(Integer topicId) { var notifications = notificationService.notifyTopicSubscribers(topicId); notifications .forEach(n -> simpMessagingTemplate.convertAndSendToUser( - n.getEmail(), toNotifyDestination(topicId), notifications)); + n.getEmail(), toNotifyMessageDest(topicId), notifications)); log.trace("All subscribers was notified by topic id = [{}]", topicId); } @@ -36,8 +36,24 @@ public void notifyAllWhoSubscribedToSameUserTopic(String userEmail) { .forEach(e -> notifyTopicSubscribers(e.getTopicId())); } - private String toNotifyDestination(Integer topicId) { + @Override + public void notifyAllPublicTopics(String email){ + var notifiedTopics = notificationService.notifyAllPublicTopicsByEmail(email); + simpMessagingTemplate.convertAndSendToUser(email, toNotifyTopicsDest(), notifiedTopics); + } + + @Override + public void notifyAllWhoSubscribedToTopic(Integer topicId) { + contactEventService.getAllByTopicId(topicId) + .forEach(e -> notifyAllPublicTopics(e.getEmail())); + } + + private String toNotifyMessageDest(Integer topicId) { return properties.getNotifyPrefix() + "/" + topicId; } + private String toNotifyTopicsDest() { + return properties.getNotifyPrefix() + "/topics"; + } + } diff --git a/src/main/java/com/chat/yourway/service/NotificationServiceImpl.java b/src/main/java/com/chat/yourway/service/NotificationServiceImpl.java index aa9c24c3..f23060ad 100644 --- a/src/main/java/com/chat/yourway/service/NotificationServiceImpl.java +++ b/src/main/java/com/chat/yourway/service/NotificationServiceImpl.java @@ -1,11 +1,13 @@ package com.chat.yourway.service; import com.chat.yourway.dto.response.MessageNotificationResponseDto; +import com.chat.yourway.dto.response.TopicNotificationResponseDto; import com.chat.yourway.mapper.MessageNotificationMapper; import com.chat.yourway.model.event.ContactEvent; import com.chat.yourway.service.interfaces.ContactEventService; import com.chat.yourway.service.interfaces.MessageService; import com.chat.yourway.service.interfaces.NotificationService; +import com.chat.yourway.service.interfaces.TopicService; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,27 +18,40 @@ @Slf4j public class NotificationServiceImpl implements NotificationService { - private final MessageService messageService; private final ContactEventService contactEventService; + private final TopicService topicService; + private final MessageService messageService; private final MessageNotificationMapper notificationMapper; + @Override public List notifyTopicSubscribers(Integer topicId) { log.trace("Started notifyTopicSubscribers by topic id [{}]", topicId); - List events = contactEventService.getAllByTopicId(topicId).stream() + return contactEventService.getAllByTopicId(topicId).stream() + .map(notificationMapper::toNotificationResponseDto) .toList(); + } - return events.stream() - .map(notificationMapper::toNotificationResponseDto) - .peek(n -> n.setUnreadMessages(countUnreadMessages(n))) + @Override + public List notifyAllPublicTopicsByEmail(String email) { + + return topicService.findAllPublic().stream() + .map(topic -> { + var event = contactEventService.getByTopicIdAndEmail(topic.getId(), email); + var topicNotificationDto = new TopicNotificationResponseDto(); + topicNotificationDto.setTopic(topic); + topicNotificationDto.setUnreadMessages(countUnreadMessages(event)); + topicNotificationDto.setLastMessage(event.getLastMessage()); + return topicNotificationDto; + }) .toList(); } - private int countUnreadMessages(MessageNotificationResponseDto notification) { + private int countUnreadMessages(ContactEvent event) { return messageService.countMessagesBetweenTimestampByTopicId( - notification.getTopicId(), - notification.getEmail(), - notification.getLastRead()); + event.getTopicId(), + event.getEmail(), + event.getTimestamp()); } } diff --git a/src/main/java/com/chat/yourway/service/interfaces/ChatNotificationService.java b/src/main/java/com/chat/yourway/service/interfaces/ChatNotificationService.java index 9a05be4b..aef3056a 100644 --- a/src/main/java/com/chat/yourway/service/interfaces/ChatNotificationService.java +++ b/src/main/java/com/chat/yourway/service/interfaces/ChatNotificationService.java @@ -16,4 +16,18 @@ public interface ChatNotificationService { */ void notifyAllWhoSubscribedToSameUserTopic(String userEmail); + /** + * Notify all topics for chat events. + * + * @param userEmail user email. + */ + void notifyAllPublicTopics(String userEmail); + + /** + * Notify everyone who is subscribed to the same topic. + * + * @param topicId The id of the topic. + */ + void notifyAllWhoSubscribedToTopic(Integer topicId); + } diff --git a/src/main/java/com/chat/yourway/service/interfaces/NotificationService.java b/src/main/java/com/chat/yourway/service/interfaces/NotificationService.java index 683cc6f2..a14ab271 100644 --- a/src/main/java/com/chat/yourway/service/interfaces/NotificationService.java +++ b/src/main/java/com/chat/yourway/service/interfaces/NotificationService.java @@ -1,6 +1,7 @@ package com.chat.yourway.service.interfaces; import com.chat.yourway.dto.response.MessageNotificationResponseDto; +import com.chat.yourway.dto.response.TopicNotificationResponseDto; import java.util.List; public interface NotificationService { @@ -13,4 +14,12 @@ public interface NotificationService { */ List notifyTopicSubscribers(Integer topicId); + /** + * Retrieves a list of notifying all public topics. + * + * @param email user email. + * @return A list of public topic's information. + */ + List notifyAllPublicTopicsByEmail(String email); + } diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 75ee8e62..3548cb92 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -50,6 +50,15 @@

WebSocket Chat Test Client


+

+ Get all topics [/user/specific/notify/topics] +

+ +
+ +
+
+

Get message history [/app/history/topic/{id}]

@@ -101,6 +110,7 @@

WebSocket Chat Test Client

const subToTopicDest = '/topic/'; const subToError = '/user/specific/error'; const subToNotificationDest = '/user/specific/notify/'; + const subToAllTopicsDest = '/user/specific/notify/topics'; const sendToPublicTopicDest = "/app/topic/public/"; const sendToPrivateTopicDest = "/app/topic/private/"; const getHistoryTopicDest = "/app/history/topic/"; @@ -117,6 +127,7 @@

WebSocket Chat Test Client

console.log('Connected: ' + frame); subscribeToError(); + subscribeToAllTopics(); }); } @@ -154,6 +165,11 @@

WebSocket Chat Test Client

response.innerText = JSON.stringify(notificationMessage); } + function showAllTopics(topics) { + let response = document.getElementById('topics'); + response.innerText = JSON.stringify(topics); + } + function subscribeToNotificationFromTopic(topicId) { stompClient.subscribe(subToNotificationDest + topicId, function (messages) { let notificationMessage = JSON.parse(messages.body); @@ -170,6 +186,14 @@

WebSocket Chat Test Client

}); } + function subscribeToAllTopics() { + stompClient.subscribe(subToAllTopicsDest, function (messages) { + let topics = JSON.parse(messages.body); + console.log(topics); + showAllTopics(topics); + }); + } + function sendToPublicTopic() { let topicId = document.getElementById('subscribeToTopic').value; let message = document.getElementById('message').value; diff --git a/src/test/java/com/chat/yourway/integration/controller/ChatControllerTest.java b/src/test/java/com/chat/yourway/integration/controller/ChatControllerTest.java index 761d16bc..86063ceb 100644 --- a/src/test/java/com/chat/yourway/integration/controller/ChatControllerTest.java +++ b/src/test/java/com/chat/yourway/integration/controller/ChatControllerTest.java @@ -202,7 +202,8 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfSubscribeEvent() { lastMessageDto.setSentFrom("vasil@gmail.com"); lastMessageDto.setLastMessage("Hi"); - var event = new ContactEvent("vasil@gmail.com", topicId, ONLINE, LocalDateTime.now(), lastMessageDto); + var event = new ContactEvent("vasil@gmail.com", topicId, ONLINE, LocalDateTime.now(), + lastMessageDto); saveContactEvent(event); //Stored subscription results for testing CompletableFuture resultKeeper = new CompletableFuture<>(); @@ -218,9 +219,7 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfSubscribeEvent() { assertThat(notifications).extracting("email").contains("vasil@gmail.com"); assertThat(notifications).extracting("topicId").contains(topicId); assertThat(notifications).extracting("status").contains(ONLINE); - assertThat(notifications).extracting("unreadMessages").isNotNull(); assertThat(notifications).extracting("lastRead").isNotNull(); - assertThat(notifications).extracting("lastMessage").isNotNull(); } @Test @@ -234,7 +233,8 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfAnyContactSubscribedTo lastMessageDto.setSentFrom("vasil@gmail.com"); lastMessageDto.setLastMessage("Hi"); - var event = new ContactEvent("vasil@gmail.com", topicId, ONLINE, LocalDateTime.now(), lastMessageDto); + var event = new ContactEvent("vasil@gmail.com", topicId, ONLINE, LocalDateTime.now(), + lastMessageDto); saveContactEvent(event); //Stored subscription results for testing CompletableFuture resultKeeper = new CompletableFuture<>(); @@ -253,9 +253,7 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfAnyContactSubscribedTo assertThat(notifications).extracting("email").contains("vasil@gmail.com"); assertThat(notifications).extracting("topicId").contains(topicId); assertThat(notifications).extracting("status").contains(SUBSCRIBED); - assertThat(notifications).extracting("unreadMessages").contains(0); assertThat(notifications).extracting("lastRead").isNotNull(); - assertThat(notifications).extracting("lastMessage").isNotNull(); } //----------------------------------- diff --git a/src/test/java/com/chat/yourway/unit/service/impl/NotificationServiceImplTest.java b/src/test/java/com/chat/yourway/unit/service/impl/NotificationServiceImplTest.java index ae4094f6..dde09bcd 100644 --- a/src/test/java/com/chat/yourway/unit/service/impl/NotificationServiceImplTest.java +++ b/src/test/java/com/chat/yourway/unit/service/impl/NotificationServiceImplTest.java @@ -4,8 +4,6 @@ import static com.chat.yourway.model.event.EventType.ONLINE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -16,7 +14,6 @@ import com.chat.yourway.model.event.ContactEvent; import com.chat.yourway.service.NotificationServiceImpl; import com.chat.yourway.service.interfaces.ContactEventService; -import com.chat.yourway.service.interfaces.MessageService; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; @@ -30,9 +27,6 @@ @ExtendWith(MockitoExtension.class) class NotificationServiceImplTest { - @Mock - private MessageService messageService; - @Mock private ContactEventService contactEventService; @@ -43,8 +37,8 @@ class NotificationServiceImplTest { private NotificationServiceImpl notificationService; @Test - @DisplayName("notifyTopicSubscribers should return a list of notifications with unread message counts") - void notifyTopicSubscribers_shouldReturnListOfNotificationsWithUnreadMessageCounts() { + @DisplayName("notifyTopicSubscribers should return a list of notifications") + void notifyTopicSubscribers_shouldReturnListOfNotifications() { // Given Integer topicId = 1; var lastMessageDto = new LastMessageResponseDto(); @@ -64,8 +58,6 @@ void notifyTopicSubscribers_shouldReturnListOfNotificationsWithUnreadMessageCoun messageNotification.setTopicId(e.getTopicId()); messageNotification.setStatus(e.getEventType()); messageNotification.setLastRead(e.getTimestamp()); - messageNotification.setUnreadMessages(1); - messageNotification.setLastMessage(e.getLastMessage()); return messageNotification; }) .toList(); @@ -73,9 +65,6 @@ void notifyTopicSubscribers_shouldReturnListOfNotificationsWithUnreadMessageCoun when(contactEventService.getAllByTopicId(topicId)).thenReturn(events); when(notificationMapper.toNotificationResponseDto(any())) .thenReturn(expectedNotifications.get(0), expectedNotifications.get(1)); - when(messageService.countMessagesBetweenTimestampByTopicId(eq(topicId), anyString(), - any(LocalDateTime.class))) - .thenReturn(1, 1); // When List result = notificationService.notifyTopicSubscribers( @@ -84,8 +73,6 @@ void notifyTopicSubscribers_shouldReturnListOfNotificationsWithUnreadMessageCoun // Then assertEquals(expectedNotifications, result, "Should return the expected list of notifications"); verify(notificationMapper, times(2)).toNotificationResponseDto(any()); - verify(messageService, times(2)).countMessagesBetweenTimestampByTopicId(eq(topicId), - anyString(), any(LocalDateTime.class)); } }