Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/com/chat/yourway/dto/response/MessageNotificationResponseDto.java
#	src/main/java/com/chat/yourway/service/ChatMessageServiceImpl.java
#	src/test/java/com/chat/yourway/integration/controller/ChatControllerTest.java
  • Loading branch information
Troha7 committed Mar 10, 2024
2 parents 984b658 + f8f3332 commit aee275f
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,4 @@ public List<MessageResponseDto> getTopicHistory(@DestinationVariable Integer top
String email = principal.getName();
return chatMessageService.sendMessageHistoryByTopicId(topicId, pageRequestDto, email);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;


}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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";
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<MessageNotificationResponseDto> notifyTopicSubscribers(Integer topicId) {
log.trace("Started notifyTopicSubscribers by topic id [{}]", topicId);

List<ContactEvent> 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<TopicNotificationResponseDto> 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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -13,4 +14,12 @@ public interface NotificationService {
*/
List<MessageNotificationResponseDto> notifyTopicSubscribers(Integer topicId);

/**
* Retrieves a list of notifying all public topics.
*
* @param email user email.
* @return A list of public topic's information.
*/
List<TopicNotificationResponseDto> notifyAllPublicTopicsByEmail(String email);

}
24 changes: 24 additions & 0 deletions src/main/resources/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ <h1>WebSocket Chat Test Client</h1>
<div id="notification"></div>
<br/>

<p style="font-style: italic">
Get all topics [/user/specific/notify/topics]
</p>

<br/>

<div id="topics"></div>
<br/>

<p style="font-style: italic">
Get message history [/app/history/topic/{id}]
</p>
Expand Down Expand Up @@ -101,6 +110,7 @@ <h1>WebSocket Chat Test Client</h1>
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/";
Expand All @@ -117,6 +127,7 @@ <h1>WebSocket Chat Test Client</h1>
console.log('Connected: ' + frame);

subscribeToError();
subscribeToAllTopics();
});
}

Expand Down Expand Up @@ -154,6 +165,11 @@ <h1>WebSocket Chat Test Client</h1>
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);
Expand All @@ -170,6 +186,14 @@ <h1>WebSocket Chat Test Client</h1>
});
}

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfSubscribeEvent() {
lastMessageDto.setSentFrom("[email protected]");
lastMessageDto.setLastMessage("Hi");

var event = new ContactEvent("[email protected]", topicId, ONLINE, LocalDateTime.now(), lastMessageDto);
var event = new ContactEvent("[email protected]", topicId, ONLINE, LocalDateTime.now(),
lastMessageDto);
saveContactEvent(event);
//Stored subscription results for testing
CompletableFuture<MessageNotificationResponseDto[]> resultKeeper = new CompletableFuture<>();
Expand All @@ -218,9 +219,7 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfSubscribeEvent() {
assertThat(notifications).extracting("email").contains("[email protected]");
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
Expand All @@ -234,7 +233,8 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfAnyContactSubscribedTo
lastMessageDto.setSentFrom("[email protected]");
lastMessageDto.setLastMessage("Hi");

var event = new ContactEvent("[email protected]", topicId, ONLINE, LocalDateTime.now(), lastMessageDto);
var event = new ContactEvent("[email protected]", topicId, ONLINE, LocalDateTime.now(),
lastMessageDto);
saveContactEvent(event);
//Stored subscription results for testing
CompletableFuture<MessageNotificationResponseDto[]> resultKeeper = new CompletableFuture<>();
Expand All @@ -253,9 +253,7 @@ void notifyTopicSubscribers_shouldNotifyTopicSubscribersIfAnyContactSubscribedTo
assertThat(notifications).extracting("email").contains("[email protected]");
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();
}

//-----------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -30,9 +27,6 @@
@ExtendWith(MockitoExtension.class)
class NotificationServiceImplTest {

@Mock
private MessageService messageService;

@Mock
private ContactEventService contactEventService;

Expand All @@ -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();
Expand All @@ -64,18 +58,13 @@ 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();

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<MessageNotificationResponseDto> result = notificationService.notifyTopicSubscribers(
Expand All @@ -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));
}

}

0 comments on commit aee275f

Please sign in to comment.