From 7aafc8d38c0ed51a37740ed376b92cad4c97f654 Mon Sep 17 00:00:00 2001 From: Hoai Viet Nguyen <viet.nguyen@th-koeln.de> Date: Wed, 9 Apr 2025 12:45:17 +0200 Subject: [PATCH] implement full chat room function --- build.gradle | 4 ++ .../websocketsdemo/configs/WebSocketConfig.kt | 2 + .../controllers/ChatRoomsController.kt | 37 +++++++++++++ .../thk/gm/websocketsdemo/dtos/ChatRoomDto.kt | 8 +++ .../handlers/ChatRoomsHandler.kt | 52 +++++++++++++++++++ .../thk/gm/websocketsdemo/models/ChatRoom.kt | 13 +++++ .../repositories/ChatRoomsRepository.kt | 8 +++ .../services/ChatRoomsService.kt | 12 +++++ .../services/ChatRoomsServiceImpl.kt | 21 ++++++++ .../templates/chatRooms/showChatRoom.ftlh | 24 +++++++++ .../templates/chatRooms/showChatRooms.ftlh | 16 ++++++ src/main/resources/templates/index.ftlh | 1 - 12 files changed, 197 insertions(+), 1 deletion(-) 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..009e09d 100644 --- a/build.gradle +++ b/build.gradle @@ -24,10 +24,14 @@ 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' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.json:org.json:chargebee-1.0' } kotlin { diff --git a/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketConfig.kt b/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketConfig.kt index 71ef4b1..708656f 100644 --- a/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketConfig.kt +++ b/src/main/kotlin/de/thk/gm/websocketsdemo/configs/WebSocketConfig.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 @@ -13,5 +14,6 @@ class WebSocketConfig () : WebSocketConfigurer { override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { registry.addHandler(EchoHandler(),"/echo") 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..4ecfdf6 --- /dev/null +++ b/src/main/kotlin/de/thk/gm/websocketsdemo/controllers/ChatRoomsController.kt @@ -0,0 +1,37 @@ +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.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import java.util.* + +@Controller +class ChatRoomsController (private var chatRoomsService: ChatRoomsService) { + @GetMapping("/chatrooms") + fun chatRooms(model: Model): String { + var chatRooms = chatRoomsService.findAll() + model.addAttribute("chatRooms", chatRooms) + return "chatRooms/showChatRooms" + } + + @GetMapping("/chatrooms/{id}") + fun chatRoom(@PathVariable("id") id: UUID, model: Model): String { + var chatRoom = chatRoomsService.findById(id) + model.addAttribute("chatRoom", chatRoom) + return "chatRooms/showChatRoom" + } + + @PostMapping("/chatrooms") + fun createChatRoom(chatRoomDto: ChatRoomDto): String { + var chatRoom = ChatRoom() + chatRoom.name = chatRoomDto.name + chatRoomsService.save(chatRoom) + return "redirect:/chatrooms/${chatRoom.id}" + } +} \ 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..4e9589e --- /dev/null +++ b/src/main/kotlin/de/thk/gm/websocketsdemo/dtos/ChatRoomDto.kt @@ -0,0 +1,8 @@ +package de.thk.gm.websocketsdemo.dtos + +import jakarta.validation.constraints.Size + +class ChatRoomDto { + @Size(min = 1, max = 100) + 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..2e554ed --- /dev/null +++ b/src/main/kotlin/de/thk/gm/websocketsdemo/handlers/ChatRoomsHandler.kt @@ -0,0 +1,52 @@ +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 +import java.util.UUID + +class ChatRoomsHandler : TextWebSocketHandler() { + + private val hashMapOfSessions: HashMap<UUID, ArrayList<WebSocketSession>> = HashMap() + override fun afterConnectionEstablished(session: WebSocketSession) { + var uri: UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build() + var roomId = UUID.fromString(uri.queryParams.getFirst("roomId")) + if (roomId != null) { + var sessions = hashMapOfSessions.get(roomId) + if (sessions == null) { + sessions = ArrayList() + } + sessions.add(session) + hashMapOfSessions[roomId] = sessions + } + } + + override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { + var uri: UriComponents = UriComponentsBuilder.fromUri(session.uri!!).build() + var roomId: UUID = UUID.fromString(uri.queryParams.getFirst("roomId")) + + var sessions: ArrayList<WebSocketSession>? = hashMapOfSessions[roomId] + 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 roomId: UUID? = UUID.fromString(uri.queryParams.getFirst("roomId")) + var sessions: ArrayList<WebSocketSession>? = hashMapOfSessions[roomId] + if (sessions != null && roomId != null) { + for (chatSession in sessions) { + if (chatSession.id == session.id) { + sessions.remove(chatSession) + hashMapOfSessions[roomId] = sessions + } + } + } + } +} \ 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..fd6cda8 --- /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..31be23a --- /dev/null +++ b/src/main/kotlin/de/thk/gm/websocketsdemo/repositories/ChatRoomsRepository.kt @@ -0,0 +1,8 @@ +package de.thk.gm.websocketsdemo.repositories + +import de.thk.gm.websocketsdemo.models.ChatRoom +import org.springframework.data.repository.CrudRepository +import java.util.* + +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..67c9bbc --- /dev/null +++ b/src/main/kotlin/de/thk/gm/websocketsdemo/services/ChatRoomsService.kt @@ -0,0 +1,12 @@ +package de.thk.gm.websocketsdemo.services + +import de.thk.gm.websocketsdemo.models.ChatRoom +import org.springframework.stereotype.Service +import java.util.* + + +interface ChatRoomsService { + fun findAll(): List<ChatRoom> + fun findById(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..b6c75ba --- /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 findAll(): List<ChatRoom> { + return chatRoomsRepository.findAll().toList() + } + + override fun findById(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..1c4e455 --- /dev/null +++ b/src/main/resources/templates/chatRooms/showChatRoom.ftlh @@ -0,0 +1,24 @@ +<#import "../layout.ftlh" as base> +<@base.layout> + <h1>Chat room: ${chatRoom.name}</h1> + <div id="chat"> + </div> + <input type="text" id="sender" placeholder="Max Mustermann"><br> + <input type="text" id="text" placeholder="Message..."> + <button type="button" onclick="sendMessage()">Send</button> + <script> + var ws = new WebSocket("/rooms?roomId=${chatRoom.id}") + var chat = document.getElementById("chat") + ws.onmessage = function (message) { + var chatMessage = JSON.parse(message.data) + chat.innerHTML += "<p><b>" +chatMessage.sender+ "</b>:" + chatMessage.text + "</p>" + } + function sendMessage(){ + var text = document.getElementById("text").value + var sender = document.getElementById("sender").value + var chatMessage = {"sender": sender, "text":text} + ws.send(JSON.stringify(chatMessage)) + } + + </script> +</@base.layout> \ 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..5f14e98 --- /dev/null +++ b/src/main/resources/templates/chatRooms/showChatRooms.ftlh @@ -0,0 +1,16 @@ +<#import "../layout.ftlh" as base> +<@base.layout> + <h2>Chat rooms</h2> + <ul> + <#list chatRooms as room> + <li><a href="/chatrooms/${room.id}">${room.name}</a></li> + </#list> + </ul> + <br> + <h2>Create chat room</h2> + <form action="/chatrooms" method="post"> + <input type="text" name="name"><br> + <button>Create</button> + </form> + +</@base.layout> \ No newline at end of file diff --git a/src/main/resources/templates/index.ftlh b/src/main/resources/templates/index.ftlh index eb82bb2..42891fb 100644 --- a/src/main/resources/templates/index.ftlh +++ b/src/main/resources/templates/index.ftlh @@ -1,5 +1,4 @@ <#import "layout.ftlh" as base> -<#import "/spring.ftl" as spring /> <@base.layout> <ul> <li><a href="/echoclient.html">Echo Client</a></li> -- GitLab