From 0bc2c85d113fc84f1754bba0b2ca9641f8123945 Mon Sep 17 00:00:00 2001
From: rabeaM <rabea.mueller@sva.de>
Date: Tue, 10 Dec 2024 14:47:01 +0100
Subject: [PATCH] Add the functions to set a user message is read

---
 .../numportal/domain/model/Message.java       | 14 +++++++
 .../domain/repository/MessageRepository.java  |  8 ++++
 .../numportal/service/MessageService.java     | 37 +++++++++++++++++++
 .../web/controller/MessageController.java     | 20 ++++++++++
 .../num-portal/V08__user_messages.sql         | 25 +++++++++----
 5 files changed, 96 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/highmed/numportal/domain/model/Message.java b/src/main/java/org/highmed/numportal/domain/model/Message.java
index 431e00a0..a3b19c9e 100644
--- a/src/main/java/org/highmed/numportal/domain/model/Message.java
+++ b/src/main/java/org/highmed/numportal/domain/model/Message.java
@@ -1,9 +1,15 @@
 package org.highmed.numportal.domain.model;
 
+import org.highmed.numportal.domain.model.admin.User;
+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 +18,7 @@
 
 import java.io.Serializable;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Entity
 @Builder
@@ -39,4 +46,11 @@ public class Message implements Serializable {
 
   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;
+
 }
diff --git a/src/main/java/org/highmed/numportal/domain/repository/MessageRepository.java b/src/main/java/org/highmed/numportal/domain/repository/MessageRepository.java
index e132e476..4da497cd 100644
--- a/src/main/java/org/highmed/numportal/domain/repository/MessageRepository.java
+++ b/src/main/java/org/highmed/numportal/domain/repository/MessageRepository.java
@@ -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);
 }
diff --git a/src/main/java/org/highmed/numportal/service/MessageService.java b/src/main/java/org/highmed/numportal/service/MessageService.java
index 9ee2b410..143c5cc2 100644
--- a/src/main/java/org/highmed/numportal/service/MessageService.java
+++ b/src/main/java/org/highmed/numportal/service/MessageService.java
@@ -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;
@@ -111,6 +120,34 @@ 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);
+    } 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);
   }
diff --git a/src/main/java/org/highmed/numportal/web/controller/MessageController.java b/src/main/java/org/highmed/numportal/web/controller/MessageController.java
index 79b2b2bd..2b412949 100644
--- a/src/main/java/org/highmed/numportal/web/controller/MessageController.java
+++ b/src/main/java/org/highmed/numportal/web/controller/MessageController.java
@@ -27,6 +27,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")
@@ -91,6 +93,24 @@ public ResponseEntity<Void> deleteUserMessage(
     messageService.deleteUserMessage(id, principal.getSubject());
     return ResponseEntity.ok().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.ok().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()));
+  }
 }
 
 
diff --git a/src/main/resources/db/migration/num-portal/V08__user_messages.sql b/src/main/resources/db/migration/num-portal/V08__user_messages.sql
index a73ec133..758a3170 100644
--- a/src/main/resources/db/migration/num-portal/V08__user_messages.sql
+++ b/src/main/resources/db/migration/num-portal/V08__user_messages.sql
@@ -1,11 +1,20 @@
 DROP TABLE IF EXISTS message;
 
-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 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,
+    sessionBased    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)
 );
\ No newline at end of file