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