Skip to content

Commit

Permalink
Feature/user messages (#663)
Browse files Browse the repository at this point in the history
* Add the functions to set a user message is read

* Message Read methos tests
  • Loading branch information
ramueSVA authored Dec 11, 2024
1 parent 3ee9f34 commit d127343
Showing 9 changed files with 164 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -37,4 +37,7 @@ public class MessageDto {
@NotNull
private MessageType type;

@NotNull
@Schema(description = "Set this value, so that a user message is readable during each session")
private boolean sessionBased;
}
16 changes: 16 additions & 0 deletions src/main/java/org/highmed/numportal/domain/model/Message.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package org.highmed.numportal.domain.model;

import org.highmed.numportal.domain.model.admin.UserDetails;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -12,6 +17,8 @@

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Builder
@@ -35,6 +42,15 @@ public class Message implements Serializable {

private MessageType type;

private boolean sessionBased;

private boolean markAsDeleted;

@OneToMany
@JoinTable(
name = "read_message_by_users",
joinColumns = @JoinColumn(name = "message_id"),
inverseJoinColumns = @JoinColumn(name = "user_details_id"))
private List<UserDetails> readByUsers = new ArrayList<>();

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package org.highmed.numportal.domain.repository;

import org.highmed.numportal.domain.model.Message;
import org.highmed.numportal.domain.model.admin.UserDetails;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public interface MessageRepository extends JpaRepository<Message, Long>, PagingAndSortingRepository<Message, Long> {

@Query("SELECT m FROM Message m WHERE :userDetails NOT MEMBER OF m.readByUsers AND m.startDate <= :now AND m.endDate >= :now")
List<Message> findAllActiveMessagesNotReadByUser(@Param("userDetails") UserDetails userDetails, @Param("now")LocalDateTime now);
}
47 changes: 45 additions & 2 deletions src/main/java/org/highmed/numportal/service/MessageService.java
Original file line number Diff line number Diff line change
@@ -2,9 +2,12 @@

import org.highmed.numportal.domain.dto.MessageDto;
import org.highmed.numportal.domain.model.Message;
import org.highmed.numportal.domain.model.admin.UserDetails;
import org.highmed.numportal.domain.repository.MessageRepository;
import org.highmed.numportal.domain.repository.UserDetailsRepository;
import org.highmed.numportal.mapper.MessageMapper;
import org.highmed.numportal.service.exception.BadRequestException;
import org.highmed.numportal.service.exception.ForbiddenException;
import org.highmed.numportal.service.exception.ResourceNotFound;

import lombok.AllArgsConstructor;
@@ -16,11 +19,16 @@
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_ACCESS_THIS_RESOURCE;
import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_DELETE_MESSAGE;
import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_HANDLE_DATE;
import static org.highmed.numportal.domain.templates.ExceptionsTemplate.CANNOT_UPDATE_MESSAGE_INVALID;
import static org.highmed.numportal.domain.templates.ExceptionsTemplate.MESSAGE_NOT_FOUND;
import static org.highmed.numportal.domain.templates.ExceptionsTemplate.USER_NOT_FOUND;


@Slf4j
@@ -29,6 +37,7 @@
public class MessageService {

private static final Safelist safeList = Safelist.simpleText().addTags("br");
private final UserDetailsRepository userDetailsRepository;

private MessageMapper messageMapper;
private UserDetailsService userDetailsService;
@@ -38,7 +47,9 @@ public class MessageService {
public MessageDto createUserMessage(MessageDto messageDto, String userId) {
userDetailsService.checkIsUserApproved(userId);
Message message = messageMapper.convertToEntity(messageDto);
message.setText(Jsoup.clean(message.getText(), safeList));
if (message.getText() != null && !message.getText().isBlank()) {
message.setText(Jsoup.clean(message.getText(), safeList));
}
validateDates(message.getStartDate(), message.getEndDate(), LocalDateTime.now().minusMinutes(5));

Message savedMessage = messageRepository.save(message);
@@ -69,10 +80,13 @@ public MessageDto updateUserMessage(Long id, MessageDto messageDto, String userI
validateDates(messageDto.getStartDate(), messageDto.getEndDate(), now);

messageToUpdate.setTitle(messageDto.getTitle());
messageToUpdate.setText(Jsoup.clean(messageDto.getText(), safeList));
if (messageToUpdate.getText() != null && !messageToUpdate.getText().isBlank()) {
messageToUpdate.setText(Jsoup.clean(messageToUpdate.getText(), safeList));
}
messageToUpdate.setStartDate(messageDto.getStartDate());
messageToUpdate.setEndDate(messageDto.getEndDate());
messageToUpdate.setType(messageDto.getType());
messageToUpdate.setSessionBased(messageDto.isSessionBased());

}
Message savedMessage = messageRepository.save(messageToUpdate);
@@ -110,6 +124,35 @@ public void deleteUserMessage(Long id, String userId) {
}
}

public void markUserMessageAsRead(Long id, String userId) {
LocalDateTime now = LocalDateTime.now();
Message readMessage = messageRepository.findById(id)
.orElseThrow(() -> new ResourceNotFound(MessageService.class, MESSAGE_NOT_FOUND,
String.format(MESSAGE_NOT_FOUND, id)));
if (readMessage.getStartDate().isBefore(now) && readMessage.getEndDate().isAfter(now) && !readMessage.isSessionBased()) {
UserDetails userDetails = userDetailsRepository.findByUserId(userId)
.orElseThrow(() -> new ResourceNotFound(MessageService.class, USER_NOT_FOUND,
String.format(USER_NOT_FOUND, userId)));
readMessage.getReadByUsers().add(userDetails);
messageRepository.save(readMessage);
} else {
throw new ForbiddenException(MessageService.class, CANNOT_ACCESS_THIS_RESOURCE);
}
}

public List<MessageDto> getAllDisplayedUserMessages(String userId) {
UserDetails userDetails = userDetailsRepository.findByUserId(userId)
.orElseThrow(() -> new ResourceNotFound(MessageService.class, USER_NOT_FOUND,
String.format(USER_NOT_FOUND, userId)));
List<Message> notReadByUserMessages = messageRepository.findAllActiveMessagesNotReadByUser(userDetails, LocalDateTime.now());
List<MessageDto> notReadByUserMessagesDto = new ArrayList<>();
for (Message message : notReadByUserMessages) {
MessageDto messageDto = messageMapper.convertToDto(message);
notReadByUserMessagesDto.add(messageDto);
}
return notReadByUserMessagesDto;
}

private static boolean isInactiveMessage(Message messageToUpdate, LocalDateTime now) {
return messageToUpdate.getEndDate().isBefore(now);
}
Original file line number Diff line number Diff line change
@@ -74,6 +74,11 @@ public GroupedOpenApi attachmentApi() {
return getDocket("Attachment", "/attachment/**", NUM_PACKAGES_TO_SCAN);
}

@Bean
public GroupedOpenApi messageApi() {
return getDocket("Message", "/message/**", NUM_PACKAGES_TO_SCAN);
}

@Bean
public OpenAPI customOpenAPI() {
OAuthFlow oAuthFlow = new OAuthFlow()
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -27,6 +28,8 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@AllArgsConstructor
@RequestMapping(value = "/message", produces = "application/json")
@@ -89,7 +92,25 @@ public ResponseEntity<Void> deleteUserMessage(
@PathVariable("id") Long id,
@AuthenticationPrincipal @NotNull Jwt principal) {
messageService.deleteUserMessage(id, principal.getSubject());
return ResponseEntity.ok().build();
return ResponseEntity.noContent().build();
}

@PostMapping(value = "/read/{id}")
@Operation(
description = "Marked a user message as read by logged in user")
public ResponseEntity<Void> markUserMessageAsRead(
@PathVariable("id") Long id,
@AuthenticationPrincipal @NotNull Jwt principal) {
messageService.markUserMessageAsRead(id, principal.getSubject());
return ResponseEntity.noContent().build();
}

@GetMapping(value = "/read")
@Operation(
description = "Get a list of all not marked and session based user messages that are currently active")
public ResponseEntity<List<MessageDto>> getAllDisplayedUserMessages(
@AuthenticationPrincipal @NotNull Jwt principal) {
return ResponseEntity.ok(messageService.getAllDisplayedUserMessages(principal.getSubject()));
}
}

25 changes: 16 additions & 9 deletions src/main/resources/db/migration/num-portal/V08__user_messages.sql
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
DROP TABLE IF EXISTS message;
CREATE TABLE message
(
id serial PRIMARY KEY,
title VARCHAR(255) NOT NULL,
text text,
start_date timestamp NOT NULL,
end_date timestamp NOT NULL,
type varchar(125) NOT NULL,
mark_as_deleted boolean,
session_based boolean
);

CREATE TABLE message(
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
text text,
start_date timestamp NOT NULL,
end_date timestamp NOT NULL,
type varchar(125) NOT NULL ,
mark_as_deleted boolean
CREATE TABLE read_message_by_users
(
message_id int REFERENCES message (id) ON UPDATE CASCADE,
user_details_id varchar(250) REFERENCES user_details (user_id) ON UPDATE CASCADE,
CONSTRAINT message_template_pkey PRIMARY KEY (message_id, user_details_id)
);
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import org.highmed.numportal.domain.dto.MessageDto;
import org.highmed.numportal.domain.model.Message;
import org.highmed.numportal.domain.model.MessageType;
import org.highmed.numportal.domain.model.admin.UserDetails;
import org.highmed.numportal.domain.repository.MessageRepository;
import org.highmed.numportal.integrationtesting.security.WithMockNumUser;

@@ -66,7 +67,7 @@ public void setUpMessage() {
.title("Active message")
.type(MessageType.INFO)
.startDate(now.minusHours(10))
.endDate(now.plusMinutes(5))
.endDate(now.plusHours(5))
.build();
messageRepository.save(activeMessage);
Message plannedMessage =
@@ -166,4 +167,22 @@ public void noAccessApiWithWrongRole() {
mockMvc.perform(delete(MESSAGE_PATH + "/{id}", 3).with(csrf())).andExpect(status().isForbidden());
}

@Test
@SneakyThrows
@WithMockNumUser(roles = {"RESEARCHER"})
public void markUserMessageAsReadTest(){
mockMvc.perform(post(MESSAGE_PATH + "/read/{id}", 2).with(csrf()))
.andExpect(status().isNoContent());
}

@SuppressWarnings("rawtypes")
@Test
@SneakyThrows
@WithMockNumUser(roles = {"RESEARCHER"})
public void getAllDisplayedUserMessagesTest(){
MvcResult result = mockMvc.perform(get(MESSAGE_PATH + "/read").with(csrf()))
.andExpect(status().isOk()).andReturn();
List readUserMessageList = mapper.readValue(result.getResponse().getContentAsString(), List.class);
Assert.assertEquals(1, readUserMessageList.size());
}
}
Original file line number Diff line number Diff line change
@@ -3,7 +3,10 @@
import org.highmed.numportal.domain.dto.MessageDto;
import org.highmed.numportal.domain.model.Message;
import org.highmed.numportal.domain.model.MessageType;
import org.highmed.numportal.domain.model.Organization;
import org.highmed.numportal.domain.model.admin.UserDetails;
import org.highmed.numportal.domain.repository.MessageRepository;
import org.highmed.numportal.domain.repository.UserDetailsRepository;
import org.highmed.numportal.mapper.MessageMapper;
import org.highmed.numportal.service.exception.BadRequestException;

@@ -17,6 +20,7 @@
import org.mockito.junit.MockitoJUnitRunner;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Optional;

import static org.mockito.Mockito.when;
@@ -29,6 +33,8 @@ public class MessageServiceTest {
@Mock
private MessageRepository messageRepository;
@Mock
private UserDetailsRepository userDetailsRepository;
@Mock
private MessageMapper messageMapper;
@Mock
private UserDetailsService userDetailsService;
@@ -54,7 +60,8 @@ public void setup() {
.text("Hier koennte deine Nachricht stehen")
.startDate(LocalDateTime.now())
.endDate(LocalDateTime.MAX)
.type(MessageType.INFO).build();
.type(MessageType.INFO)
.readByUsers(new ArrayList<>()).build();

updateMessageDto = MessageDto.builder()
.title("Neue Serverzeiten")
@@ -157,5 +164,26 @@ public void deleteUserMessage_CannotDeleteMessageTest() {

Assert.assertThrows(BadRequestException.class, () -> messageService.deleteUserMessage(messageId, userId));
}

@Test
public void markMessageAsReadTest() {
Message readMessage = Message.builder()
.title("Other title")
.text("Hier koennte deine Nachricht stehen")
.startDate(message.getStartDate())
.endDate(LocalDateTime.MAX)
.type(MessageType.INFO)
.readByUsers(new ArrayList<>()).build();
UserDetails userDetails = new UserDetails();
readMessage.getReadByUsers().add(userDetails);
when(messageRepository.findById(1L)).thenReturn(Optional.ofNullable(message));
when(userDetailsRepository.findByUserId(USER_ID)).thenReturn(Optional.of(userDetails));
when(messageRepository.save(readMessage)).thenReturn(readMessage);
messageService.markUserMessageAsRead(1L, USER_ID);

Mockito.verify(messageRepository, Mockito.times(1)).findById(1L);
Mockito.verify(userDetailsRepository, Mockito.times(1)).findByUserId(USER_ID);
Mockito.verify(messageRepository, Mockito.times(1)).save(readMessage);
}
}

0 comments on commit d127343

Please sign in to comment.