From 131fc9ea43276fafe8a7d1a20df35122a99e1a43 Mon Sep 17 00:00:00 2001
From: Hoai Viet Nguyen <viet.nguyen@th-koeln.de>
Date: Fri, 11 Apr 2025 23:10:41 +0200
Subject: [PATCH] implement functionality which make chat messages persistent

---
 build.gradle                                  |  1 +
 .../configs/WebSocketsConfig.kt               |  6 +++--
 .../controllers/ChatRoomsController.kt        |  6 ++++-
 .../handlers/ChatRoomsHandler.kt              | 20 ++++++++++++++--
 .../gm/websocketsdemo/models/ChatMessage.kt   | 19 +++++++++++++++
 .../repositories/ChatMessageRepository.kt     | 12 ++++++++++
 .../services/ChatMessageService.kt            |  9 +++++++
 .../services/ChatMessageServiceImpl.kt        | 24 +++++++++++++++++++
 .../templates/chatRooms/showChatRoom.ftlh     |  6 ++++-
 9 files changed, 97 insertions(+), 6 deletions(-)
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatMessage.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatMessageRepository.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageService.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageServiceImpl.kt

diff --git a/build.gradle b/build.gradle
index 73893fa..6e55332 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,6 +27,7 @@ dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
     implementation 'org.springframework.boot:spring-boot-starter-validation'
     testImplementation 'org.springframework.boot:spring-boot-starter-test'
+    implementation 'org.json:org.json:chargebee-1.0'
     testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
     developmentOnly 'org.springframework.boot:spring-boot-devtools'
     runtimeOnly 'com.h2database:h2'
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketsConfig.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketsConfig.kt
index 7ef2d66..b12c62e 100644
--- a/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketsConfig.kt
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketsConfig.kt
@@ -3,6 +3,8 @@ package de.thk.gm.websocketsdemo.configs
 import de.thk.gm.websocketsdemo.handlers.ChatRoomsHandler
 import de.thk.gm.websocketsdemo.handlers.EchoHandler
 import de.thk.gm.websocketsdemo.handlers.SimpleChatHandler
+import de.thk.gm.websocketsdemo.services.ChatMessageService
+import de.thk.gm.websocketsdemo.services.ChatRoomsService
 import org.springframework.context.annotation.Configuration
 import org.springframework.web.socket.WebSocketHandler
 import org.springframework.web.socket.config.annotation.EnableWebSocket
@@ -11,10 +13,10 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
 
 @Configuration
 @EnableWebSocket
-class WebSocketsConfig(): WebSocketConfigurer {
+class WebSocketsConfig(private val chatMessageService: ChatMessageService, private val chatRoomsService: ChatRoomsService): WebSocketConfigurer {
     override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
         registry.addHandler(EchoHandler(), "/echo").setAllowedOrigins("*")
         registry.addHandler(SimpleChatHandler(), "/chat")
-        registry.addHandler(ChatRoomsHandler(),"/rooms")
+        registry.addHandler(ChatRoomsHandler(chatRoomsService, chatMessageService),"/rooms")
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt
index 36fdf8c..da08a7a 100644
--- a/src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt
@@ -2,6 +2,7 @@ package de.thk.gm.websocketsdemo.controllers
 
 import de.thk.gm.websocketsdemo.dtos.ChatRoomDto
 import de.thk.gm.websocketsdemo.models.ChatRoom
+import de.thk.gm.websocketsdemo.services.ChatMessageService
 import de.thk.gm.websocketsdemo.services.ChatRoomsService
 import org.springframework.stereotype.Controller
 import org.springframework.ui.Model
@@ -10,7 +11,7 @@ import java.util.*
 
 @Controller
 @RequestMapping("/chatrooms")
-class ChatRoomsController (private val chatRoomsService: ChatRoomsService) {
+class ChatRoomsController (private val chatRoomsService: ChatRoomsService, private val chatMessageService: ChatMessageService) {
 
     @GetMapping
     fun getChatRooms(model: Model): String {
@@ -28,8 +29,11 @@ class ChatRoomsController (private val chatRoomsService: ChatRoomsService) {
 
     @GetMapping("/{id}")
     fun getChatRoom(@PathVariable("id") id: UUID, model: Model): String {
+
         var chatRoom = chatRoomsService.getChatRoomById(id)
+        var chatMessages = chatMessageService.getChatMessagesByChatRoomId(id)
         model.addAttribute("chatRoom", chatRoom)
+        model.addAttribute("chatMessages", chatMessages)
         return "chatRooms/showChatRoom"
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt
index 9d80ede..f6f25e5 100644
--- a/src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt
@@ -1,13 +1,20 @@
 package de.thk.gm.websocketsdemo.handlers
 
+import de.thk.gm.websocketsdemo.models.ChatMessage
+import de.thk.gm.websocketsdemo.services.ChatMessageService
+import de.thk.gm.websocketsdemo.services.ChatRoomsService
+import org.json.JSONObject
 import org.springframework.web.socket.CloseStatus
 import org.springframework.web.socket.TextMessage
 import org.springframework.web.socket.WebSocketSession
 import org.springframework.web.socket.handler.TextWebSocketHandler
 import org.springframework.web.util.UriComponents
 import org.springframework.web.util.UriComponentsBuilder
+import java.util.*
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
 
-class ChatRoomsHandler : TextWebSocketHandler() {
+class ChatRoomsHandler (private val chatRoomsService: ChatRoomsService, private val chatMessageService: ChatMessageService) : TextWebSocketHandler() {
     private val hashMapOfSessions : HashMap<String, ArrayList<WebSocketSession>> = HashMap()
     override fun afterConnectionEstablished(session: WebSocketSession) {
         var uri : UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build()
@@ -24,8 +31,17 @@ class ChatRoomsHandler : TextWebSocketHandler() {
 
     override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
         var uri : UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build()
-        var id = uri.queryParams.getFirst("id")
+        var id  = uri.queryParams.getFirst("id")
         var sessions = hashMapOfSessions[id]
+        val chatMessageJSONObject = JSONObject(message.payload)
+
+        val chatRoom = chatRoomsService.getChatRoomById(UUID.fromString(id));
+        val chatMessage = ChatMessage()
+        chatMessage.chatRoom = chatRoom
+        chatMessage.text = chatMessageJSONObject.getString("text")
+        chatMessage.username = chatMessageJSONObject.getString("username")
+        chatMessageService.save(chatMessage)
+
         if(sessions != null) {
             for (chatSession in sessions) {
                 chatSession.sendMessage(message)
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatMessage.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatMessage.kt
new file mode 100644
index 0000000..1d55057
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatMessage.kt
@@ -0,0 +1,19 @@
+package de.thk.gm.websocketsdemo.models
+
+import jakarta.persistence.Entity
+import jakarta.persistence.Id
+import jakarta.persistence.ManyToOne
+import java.util.*
+
+@Entity
+class ChatMessage {
+    @Id
+    val id: UUID = UUID.randomUUID()
+
+    var username: String? = null
+    var text: String? = null
+    var createdAt: Date = Date()
+
+    @ManyToOne
+    var chatRoom: ChatRoom? = null
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatMessageRepository.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatMessageRepository.kt
new file mode 100644
index 0000000..15932e0
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatMessageRepository.kt
@@ -0,0 +1,12 @@
+package de.thk.gm.websocketsdemo.repositories
+
+import de.thk.gm.websocketsdemo.models.ChatMessage
+import de.thk.gm.websocketsdemo.models.ChatRoom
+import org.springframework.data.repository.CrudRepository
+import org.springframework.stereotype.Repository
+import java.util.*
+
+@Repository
+interface ChatMessageRepository : CrudRepository<ChatMessage, UUID> {
+    fun findChatMessagesByChatRoomOrderByCreatedAt(chatRoom: ChatRoom): List<ChatMessage>
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageService.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageService.kt
new file mode 100644
index 0000000..ba1d2fb
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageService.kt
@@ -0,0 +1,9 @@
+package de.thk.gm.websocketsdemo.services
+
+import de.thk.gm.websocketsdemo.models.ChatMessage
+import java.util.*
+
+interface ChatMessageService {
+    fun getChatMessagesByChatRoomId(roomId: UUID): List<ChatMessage>
+    fun save(chatMessage: ChatMessage)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageServiceImpl.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageServiceImpl.kt
new file mode 100644
index 0000000..2248e81
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatMessageServiceImpl.kt
@@ -0,0 +1,24 @@
+package de.thk.gm.websocketsdemo.services
+
+import de.thk.gm.websocketsdemo.models.ChatMessage
+import de.thk.gm.websocketsdemo.repositories.ChatMessageRepository
+import de.thk.gm.websocketsdemo.repositories.ChatRoomsRepository
+import org.springframework.stereotype.Service
+import java.util.*
+
+@Service
+class ChatMessageServiceImpl (private val chatMessageRepository: ChatMessageRepository, private val chatRoomsRepository: ChatRoomsRepository) : ChatMessageService {
+    override fun getChatMessagesByChatRoomId(roomId: UUID): List<ChatMessage> {
+        val chatRoom = chatRoomsRepository.findById(roomId).orElse(null)
+        if(chatRoom != null) {
+            return chatMessageRepository.findChatMessagesByChatRoomOrderByCreatedAt(chatRoom)
+        } else {
+            return listOf()
+        }
+
+    }
+
+    override fun save(chatMessage: ChatMessage) {
+       chatMessageRepository.save(chatMessage)
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/templates/chatRooms/showChatRoom.ftlh b/src/main/resources/templates/chatRooms/showChatRoom.ftlh
index 8aaac5e..f5530e7 100644
--- a/src/main/resources/templates/chatRooms/showChatRoom.ftlh
+++ b/src/main/resources/templates/chatRooms/showChatRoom.ftlh
@@ -13,7 +13,11 @@
     <input type="text" id="text" placeholder="message">
     <br>
     <button onclick="sendMessage()">Send</button>
-    <div id="chat"></div>
+    <div id="chat">
+        <#list chatMessages as chatMessage>
+            <p><b>${chatMessage.username}:</b>${chatMessage.text}</p>
+        </#list>
+    </div>
     <script>
         var ws = new WebSocket("/rooms?id=${chatRoom.id}")
         var chat = document.getElementById("chat")
-- 
GitLab