diff --git a/build.gradle b/build.gradle index efa4a43452e7e9a2bf2debfa357037c30d4465be..009e09d9ab1a6eb158afcdd4512ff50bc3a218ec 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 71ef4b1764c36da031bbf9166e63c0a067f6683e..708656f4bb97c895a9d12a0a8031ff67b02d46fa 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 0000000000000000000000000000000000000000..4ecfdf60a707e3bc69de0375e6676325b888fdc6 --- /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 0000000000000000000000000000000000000000..4e9589ecdb21e37913bd909194b404e707e9311c --- /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 0000000000000000000000000000000000000000..2e554ed936c3556483c78071c223421196571c42 --- /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 0000000000000000000000000000000000000000..fd6cda85cd24c88883322ed2c5471525af69cdde --- /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 0000000000000000000000000000000000000000..31be23a831f97c32c8fbce55ab3c8c452b781146 --- /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 0000000000000000000000000000000000000000..67c9bbcb855d32b5fc99a1ceede47ab0636de513 --- /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 0000000000000000000000000000000000000000..b6c75ba200a5b722d8348d671d88dbeb9223d087 --- /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 0000000000000000000000000000000000000000..1c4e455f2c140b66567c6dc36a8cdd9c8c80d883 --- /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 0000000000000000000000000000000000000000..5f14e981f82e7526115d25a2ee13d33167d09acb --- /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 eb82bb2df6d80ff03345e1346e46359de961020d..42891fb59ea111d6571a964459a89f9e0ef840f0 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>