Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/api-specification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ components:
description: 채팅방 고유 식별자
title:
type: string
description: 채팅방 제목(Guide-User 조합)
description: 채팅방 내부 식별용 기본 제목 (Guide-사용자 ID 조합)
displayTitle:
type: string
description: 요청자 기준 상대 닉네임을 포함한 표현용 제목 (예: "홍길동님과의 채팅")
guideId:
type: integer
format: int64
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class ChatRoomController(
): ResponseEntity<ApiResponse<ChatRoomResponse>> {
val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.")
val room = roomService.createOneToOneRoom(req.guideId, req.userId, authenticatedId)
return ResponseEntity.ok(ApiResponse(msg = "채팅방 시작", data = ChatRoomResponse.from(room)))
val responseData = roomService.toResponse(room, authenticatedId)
return ResponseEntity.ok(ApiResponse(msg = "채팅방 시작", data = responseData))
}

@DeleteMapping("/{roomId}")
Expand All @@ -61,7 +62,7 @@ class ChatRoomController(
@AuthenticationPrincipal requesterId: Long?,
): ResponseEntity<ApiResponse<ChatRoomResponse>> {
val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.")
val room = roomService.get(roomId, authenticatedId)
return ResponseEntity.ok(ApiResponse(msg = "채팅방 조회", data = ChatRoomResponse.from(room)))
val responseData = roomService.getResponse(roomId, authenticatedId)
return ResponseEntity.ok(ApiResponse(msg = "채팅방 조회", data = responseData))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ import java.time.ZonedDateTime
data class ChatRoomResponse(
val id: Long?,
val title: String,
val displayTitle: String,
val guideId: Long,
val userId: Long,
val updatedAt: ZonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")),
val lastMessageId: Long?,
) {
companion object {
fun from(room: ChatRoom): ChatRoomResponse {
fun from(
room: ChatRoom,
displayTitle: String? = null,
): ChatRoomResponse {
return ChatRoomResponse(
id = room.id,
title = room.title,
displayTitle = displayTitle ?: room.title,
guideId = room.guideId,
userId = room.userId,
updatedAt = room.updatedAt,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.back.koreaTravelGuide.domain.userChat.chatroom.service

import com.back.koreaTravelGuide.domain.user.entity.User
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
import com.back.koreaTravelGuide.domain.userChat.chatmessage.repository.ChatMessageRepository
import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomListResponse
import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomResponse
Expand All @@ -17,6 +19,7 @@ import java.util.NoSuchElementException
class ChatRoomService(
private val roomRepository: ChatRoomRepository,
private val messageRepository: ChatMessageRepository,
private val userRepository: UserRepository,
) {
@Transactional
fun createOneToOneRoom(
Expand Down Expand Up @@ -74,7 +77,11 @@ class ChatRoomService(
val (cursorUpdatedAt, cursorRoomId) = parseCursor(cursor)
val pageable = PageRequest.of(0, limit)
val rooms = roomRepository.findPagedByMember(requesterId, cursorUpdatedAt, cursorRoomId, pageable)
val roomResponses = rooms.map(ChatRoomResponse::from)
val usersById = loadUsersFor(rooms)
val roomResponses =
rooms.map { room ->
toResponse(room, requesterId, usersById)
}
val nextCursor =
if (roomResponses.size < limit) {
null
Expand All @@ -90,6 +97,52 @@ class ChatRoomService(
)
}

@Transactional(readOnly = true)
fun getResponse(
roomId: Long,
requesterId: Long,
): ChatRoomResponse {
val room = get(roomId, requesterId)
val usersById = loadUsersFor(listOf(room))
return toResponse(room, requesterId, usersById)
}

fun toResponse(
room: ChatRoom,
viewerId: Long,
): ChatRoomResponse {
val usersById = loadUsersFor(listOf(room))
return toResponse(room, viewerId, usersById)
}

private fun toResponse(
room: ChatRoom,
viewerId: Long,
cachedUsers: Map<Long, User>,
): ChatRoomResponse {
val displayTitle = buildDisplayTitle(room, viewerId, cachedUsers)
return ChatRoomResponse.from(room, displayTitle)
}

private fun loadUsersFor(rooms: Collection<ChatRoom>): Map<Long, User> {
val ids = rooms.flatMap { listOf(it.guideId, it.userId) }.toSet()
if (ids.isEmpty()) {
return emptyMap()
}
return userRepository.findAllById(ids).associateBy { it.id!! }
}

private fun buildDisplayTitle(
room: ChatRoom,
viewerId: Long,
cachedUsers: Map<Long, User>,
): String {
val guideNickname = cachedUsers[room.guideId]?.nickname ?: "Guide-${room.guideId}"
val userNickname = cachedUsers[room.userId]?.nickname ?: "User-${room.userId}"
val counterpartName = if (viewerId == room.guideId) userNickname else guideNickname
return "${counterpartName}님과의 채팅"
}

private fun parseCursor(cursor: String?): Pair<ZonedDateTime?, Long?> {
if (cursor.isNullOrBlank()) {
return null to null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class ChatRoomControllerTest {
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.rooms.length()").value(3))
.andExpect(jsonPath("$.data.rooms[0].id").value(existingRoom.id!!.toInt()))
.andExpect(jsonPath("$.data.rooms[0].displayTitle").value("${guide.nickname}님과의 채팅"))
.andReturn()

val firstCursorNode = objectMapper.readTree(firstPage.response.contentAsString)["data"]["nextCursor"]
Expand All @@ -139,7 +140,8 @@ class ChatRoomControllerTest {
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.rooms.length()").value(3))
.andExpect(jsonPath("$.data.rooms[0].id").exists()) // 도커 환경에서 정렬 순서가 다를 수 있어 ID 존재만 확인
.andExpect(jsonPath("$.data.rooms[0].id").value(extraRooms[2].id!!.toInt()))
.andExpect(jsonPath("$.data.rooms[0].displayTitle").value("guide3님과의 채팅"))
}

@Test
Expand All @@ -156,6 +158,7 @@ class ChatRoomControllerTest {
.andExpect(jsonPath("$.data.id").value(existingRoom.id!!.toInt()))
.andExpect(jsonPath("$.data.guideId").value(guide.id!!.toInt()))
.andExpect(jsonPath("$.data.userId").value(guest.id!!.toInt()))
.andExpect(jsonPath("$.data.displayTitle").value("${guide.nickname}님과의 채팅"))
}

@Test
Expand All @@ -168,6 +171,7 @@ class ChatRoomControllerTest {
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.id").value(existingRoom.id!!.toInt()))
.andExpect(jsonPath("$.data.title").value(existingRoom.title))
.andExpect(jsonPath("$.data.displayTitle").value("${guest.nickname}님과의 채팅"))
}

@Test
Expand Down