From 26e8e691a9cefe9c0ced352703a2cb555188fe68 Mon Sep 17 00:00:00 2001
From: Hoai Viet Nguyen <viet.nguyen@th-koeln.de>
Date: Thu, 10 Apr 2025 23:23:47 +0200
Subject: [PATCH] add chatrooms

---
 build.gradle                                  |  4 ++
 .../configs/WebSocketsConfig.kt               |  2 +
 .../controllers/ChatRoomsController.kt        | 35 +++++++++++++++
 .../thk/gm/websocketsdemo/dtos/ChatRoomDto.kt | 10 +++++
 .../handlers/ChatRoomsHandler.kt              | 45 +++++++++++++++++++
 .../thk/gm/websocketsdemo/models/ChatRoom.kt  | 13 ++++++
 .../repositories/ChatRoomsRepository.kt       | 10 +++++
 .../services/ChatRoomsService.kt              | 10 +++++
 .../services/ChatRoomsServiceImpl.kt          | 21 +++++++++
 .../templates/chatRooms/showChatRoom.ftlh     | 33 ++++++++++++++
 .../templates/chatRooms/showChatRooms.ftlh    | 22 +++++++++
 11 files changed, 205 insertions(+)
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/dtos/ChatRoomDto.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatRoom.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatRoomsRepository.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsService.kt
 create mode 100644 src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsServiceImpl.kt
 create mode 100644 src/main/resources/templates/chatRooms/showChatRoom.ftlh
 create mode 100644 src/main/resources/templates/chatRooms/showChatRooms.ftlh

diff --git a/build.gradle b/build.gradle
index efa4a43..73893fa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,9 +24,13 @@ dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-websocket'
     implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
     implementation 'org.jetbrains.kotlin:kotlin-reflect'
+    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'
     testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
     developmentOnly 'org.springframework.boot:spring-boot-devtools'
+    runtimeOnly 'com.h2database:h2'
+    runtimeOnly 'org.postgresql:postgresql'
     testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
 }
 
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 72d1e33..7ef2d66 100644
--- a/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketsConfig.kt
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketsConfig.kt
@@ -1,5 +1,6 @@
 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 org.springframework.context.annotation.Configuration
@@ -14,5 +15,6 @@ class WebSocketsConfig(): WebSocketConfigurer {
     override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
         registry.addHandler(EchoHandler(), "/echo").setAllowedOrigins("*")
         registry.addHandler(SimpleChatHandler(), "/chat")
+        registry.addHandler(ChatRoomsHandler(),"/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
new file mode 100644
index 0000000..36fdf8c
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt
@@ -0,0 +1,35 @@
+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.ChatRoomsService
+import org.springframework.stereotype.Controller
+import org.springframework.ui.Model
+import org.springframework.web.bind.annotation.*
+import java.util.*
+
+@Controller
+@RequestMapping("/chatrooms")
+class ChatRoomsController (private val chatRoomsService: ChatRoomsService) {
+
+    @GetMapping
+    fun getChatRooms(model: Model): String {
+        model.addAttribute("chatRooms", chatRoomsService.getChatRooms())
+        return "chatRooms/showChatRooms"
+    }
+
+    @PostMapping
+    fun addChatRoom(chatRoomDto: ChatRoomDto): String {
+        var chatRoom = ChatRoom()
+        chatRoom.name = chatRoomDto.name
+        chatRoomsService.save(chatRoom)
+        return "redirect:/chatrooms/${chatRoom.id}"
+    }
+
+    @GetMapping("/{id}")
+    fun getChatRoom(@PathVariable("id") id: UUID, model: Model): String {
+        var chatRoom = chatRoomsService.getChatRoomById(id)
+        model.addAttribute("chatRoom", chatRoom)
+        return "chatRooms/showChatRoom"
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/dtos/ChatRoomDto.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/dtos/ChatRoomDto.kt
new file mode 100644
index 0000000..2d827a1
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/dtos/ChatRoomDto.kt
@@ -0,0 +1,10 @@
+package de.thk.gm.websocketsdemo.dtos
+
+import jakarta.validation.constraints.NotNull
+import jakarta.validation.constraints.Size
+
+class ChatRoomDto {
+    @NotNull
+    @Size(min = 1, max = 50)
+    var name: String = ""
+}
\ 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
new file mode 100644
index 0000000..9d80ede
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt
@@ -0,0 +1,45 @@
+package de.thk.gm.websocketsdemo.handlers
+
+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
+
+class ChatRoomsHandler : TextWebSocketHandler() {
+    private val hashMapOfSessions : HashMap<String, ArrayList<WebSocketSession>> = HashMap()
+    override fun afterConnectionEstablished(session: WebSocketSession) {
+        var uri : UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build()
+        var id = uri.queryParams.getFirst("id")
+        if(id != null) {
+            var sessions = hashMapOfSessions[id]
+            if(sessions == null) {
+                sessions = ArrayList()
+            }
+            sessions.add(session)
+            hashMapOfSessions[id] = sessions
+        }
+    }
+
+    override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
+        var uri : UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build()
+        var id = uri.queryParams.getFirst("id")
+        var sessions = hashMapOfSessions[id]
+        if(sessions != null) {
+            for (chatSession in sessions) {
+                chatSession.sendMessage(message)
+            }
+        }
+    }
+
+    override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
+        var uri : UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build()
+        var id = uri.queryParams.getFirst("id")
+        var sessions = hashMapOfSessions[id]
+        if(sessions != null) {
+            sessions.remove(session)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatRoom.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatRoom.kt
new file mode 100644
index 0000000..8d98ce6
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/models/ChatRoom.kt
@@ -0,0 +1,13 @@
+package de.thk.gm.websocketsdemo.models
+
+import jakarta.persistence.Entity
+import jakarta.persistence.Id
+import java.util.*
+
+@Entity
+class ChatRoom {
+    @Id
+    var id : UUID = UUID.randomUUID()
+
+    var name: String = ""
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatRoomsRepository.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatRoomsRepository.kt
new file mode 100644
index 0000000..96cc7f8
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatRoomsRepository.kt
@@ -0,0 +1,10 @@
+package de.thk.gm.websocketsdemo.repositories
+
+import de.thk.gm.websocketsdemo.models.ChatRoom
+import org.springframework.data.repository.CrudRepository
+import org.springframework.stereotype.Repository
+import java.util.*
+
+@Repository
+interface ChatRoomsRepository : CrudRepository<ChatRoom, UUID> {
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsService.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsService.kt
new file mode 100644
index 0000000..fb6270b
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsService.kt
@@ -0,0 +1,10 @@
+package de.thk.gm.websocketsdemo.services
+
+import de.thk.gm.websocketsdemo.models.ChatRoom
+import java.util.*
+
+interface ChatRoomsService {
+    fun getChatRooms(): List<ChatRoom>
+    fun getChatRoomById(id: UUID): ChatRoom?
+    fun save(chatRoom: ChatRoom)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsServiceImpl.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsServiceImpl.kt
new file mode 100644
index 0000000..2ed307c
--- /dev/null
+++ b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsServiceImpl.kt
@@ -0,0 +1,21 @@
+package de.thk.gm.websocketsdemo.services
+
+import de.thk.gm.websocketsdemo.models.ChatRoom
+import de.thk.gm.websocketsdemo.repositories.ChatRoomsRepository
+import org.springframework.stereotype.Service
+import java.util.*
+
+@Service
+class ChatRoomsServiceImpl (private val chatRoomsRepository: ChatRoomsRepository) : ChatRoomsService {
+    override fun getChatRooms(): List<ChatRoom> {
+        return chatRoomsRepository.findAll().toList()
+    }
+
+    override fun getChatRoomById(id: UUID): ChatRoom? {
+        return chatRoomsRepository.findById(id).orElse(null)
+    }
+
+    override fun save(chatRoom: ChatRoom) {
+        chatRoomsRepository.save(chatRoom)
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/templates/chatRooms/showChatRoom.ftlh b/src/main/resources/templates/chatRooms/showChatRoom.ftlh
new file mode 100644
index 0000000..8aaac5e
--- /dev/null
+++ b/src/main/resources/templates/chatRooms/showChatRoom.ftlh
@@ -0,0 +1,33 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport"
+          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>Chat Room</title>
+</head>
+<body>
+    <h1>Chat room ${chatRoom.name}</h1>
+    <input type="text" id="username" placeholder="username">
+    <input type="text" id="text" placeholder="message">
+    <br>
+    <button onclick="sendMessage()">Send</button>
+    <div id="chat"></div>
+    <script>
+        var ws = new WebSocket("/rooms?id=${chatRoom.id}")
+        var chat = document.getElementById("chat")
+        var message = {}
+        function sendMessage(){
+            message.username = document.getElementById("username").value
+            message.text = document.getElementById("text").value
+            ws.send(JSON.stringify(message))
+        }
+
+        ws.onmessage = function (msg) {
+            message = JSON.parse(msg.data)
+            chat.innerHTML += "<p><b>"+message.username+":</b>" + message.text + "</p>"
+        }
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/chatRooms/showChatRooms.ftlh b/src/main/resources/templates/chatRooms/showChatRooms.ftlh
new file mode 100644
index 0000000..69116bc
--- /dev/null
+++ b/src/main/resources/templates/chatRooms/showChatRooms.ftlh
@@ -0,0 +1,22 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport"
+          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>Chat Rooms</title>
+</head>
+<body>
+    <h1>Chat Rooms</h1>
+    <ul>
+    <#list chatRooms as room>
+       <li><a href="/chatrooms/${room.id}">${room.name}</a></li>
+    </#list>
+    </ul><br>
+    <form action="/chatrooms" method="post">
+        <input name="name" type="text"><br>
+        <button>Create chat room</button>
+    </form>
+</body>
+</html>
\ No newline at end of file
-- 
GitLab