Skip to content

Commit 252a2eb

Browse files
authored
feat(be): 유저,가이드 채팅방 제목 관련 로직 추가 (#142)
feat(be): 유저,가이드 채팅방 제목 관련 로직 추가 (#142) docs(api): describe displayTitle for chat room responses
1 parent ce5928f commit 252a2eb

File tree

5 files changed

+73
-7
lines changed

5 files changed

+73
-7
lines changed

docs/api-specification.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ components:
182182
description: 채팅방 고유 식별자
183183
title:
184184
type: string
185-
description: 채팅방 제목(Guide-User 조합)
185+
description: 채팅방 내부 식별용 기본 제목 (Guide-사용자 ID 조합)
186+
displayTitle:
187+
type: string
188+
description: 요청자 기준 상대 닉네임을 포함한 표현용 제목 (예: "홍길동님과의 채팅")
186189
guideId:
187190
type: integer
188191
format: int64

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomController.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ class ChatRoomController(
4242
): ResponseEntity<ApiResponse<ChatRoomResponse>> {
4343
val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.")
4444
val room = roomService.createOneToOneRoom(req.guideId, req.userId, authenticatedId)
45-
return ResponseEntity.ok(ApiResponse(msg = "채팅방 시작", data = ChatRoomResponse.from(room)))
45+
val responseData = roomService.toResponse(room, authenticatedId)
46+
return ResponseEntity.ok(ApiResponse(msg = "채팅방 시작", data = responseData))
4647
}
4748

4849
@DeleteMapping("/{roomId}")
@@ -61,7 +62,7 @@ class ChatRoomController(
6162
@AuthenticationPrincipal requesterId: Long?,
6263
): ResponseEntity<ApiResponse<ChatRoomResponse>> {
6364
val authenticatedId = requesterId ?: throw AccessDeniedException("인증이 필요합니다.")
64-
val room = roomService.get(roomId, authenticatedId)
65-
return ResponseEntity.ok(ApiResponse(msg = "채팅방 조회", data = ChatRoomResponse.from(room)))
65+
val responseData = roomService.getResponse(roomId, authenticatedId)
66+
return ResponseEntity.ok(ApiResponse(msg = "채팅방 조회", data = responseData))
6667
}
6768
}

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/dto/ChatRoomResponse.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import java.time.ZonedDateTime
77
data class ChatRoomResponse(
88
val id: Long?,
99
val title: String,
10+
val displayTitle: String,
1011
val guideId: Long,
1112
val userId: Long,
1213
val updatedAt: ZonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")),
1314
val lastMessageId: Long?,
1415
) {
1516
companion object {
16-
fun from(room: ChatRoom): ChatRoomResponse {
17+
fun from(
18+
room: ChatRoom,
19+
displayTitle: String? = null,
20+
): ChatRoomResponse {
1721
return ChatRoomResponse(
1822
id = room.id,
1923
title = room.title,
24+
displayTitle = displayTitle ?: room.title,
2025
guideId = room.guideId,
2126
userId = room.userId,
2227
updatedAt = room.updatedAt,

src/main/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/service/ChatRoomService.kt

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.back.koreaTravelGuide.domain.userChat.chatroom.service
22

3+
import com.back.koreaTravelGuide.domain.user.entity.User
4+
import com.back.koreaTravelGuide.domain.user.repository.UserRepository
35
import com.back.koreaTravelGuide.domain.userChat.chatmessage.repository.ChatMessageRepository
46
import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomListResponse
57
import com.back.koreaTravelGuide.domain.userChat.chatroom.dto.ChatRoomResponse
@@ -17,6 +19,7 @@ import java.util.NoSuchElementException
1719
class ChatRoomService(
1820
private val roomRepository: ChatRoomRepository,
1921
private val messageRepository: ChatMessageRepository,
22+
private val userRepository: UserRepository,
2023
) {
2124
@Transactional
2225
fun createOneToOneRoom(
@@ -74,7 +77,11 @@ class ChatRoomService(
7477
val (cursorUpdatedAt, cursorRoomId) = parseCursor(cursor)
7578
val pageable = PageRequest.of(0, limit)
7679
val rooms = roomRepository.findPagedByMember(requesterId, cursorUpdatedAt, cursorRoomId, pageable)
77-
val roomResponses = rooms.map(ChatRoomResponse::from)
80+
val usersById = loadUsersFor(rooms)
81+
val roomResponses =
82+
rooms.map { room ->
83+
toResponse(room, requesterId, usersById)
84+
}
7885
val nextCursor =
7986
if (roomResponses.size < limit) {
8087
null
@@ -90,6 +97,52 @@ class ChatRoomService(
9097
)
9198
}
9299

100+
@Transactional(readOnly = true)
101+
fun getResponse(
102+
roomId: Long,
103+
requesterId: Long,
104+
): ChatRoomResponse {
105+
val room = get(roomId, requesterId)
106+
val usersById = loadUsersFor(listOf(room))
107+
return toResponse(room, requesterId, usersById)
108+
}
109+
110+
fun toResponse(
111+
room: ChatRoom,
112+
viewerId: Long,
113+
): ChatRoomResponse {
114+
val usersById = loadUsersFor(listOf(room))
115+
return toResponse(room, viewerId, usersById)
116+
}
117+
118+
private fun toResponse(
119+
room: ChatRoom,
120+
viewerId: Long,
121+
cachedUsers: Map<Long, User>,
122+
): ChatRoomResponse {
123+
val displayTitle = buildDisplayTitle(room, viewerId, cachedUsers)
124+
return ChatRoomResponse.from(room, displayTitle)
125+
}
126+
127+
private fun loadUsersFor(rooms: Collection<ChatRoom>): Map<Long, User> {
128+
val ids = rooms.flatMap { listOf(it.guideId, it.userId) }.toSet()
129+
if (ids.isEmpty()) {
130+
return emptyMap()
131+
}
132+
return userRepository.findAllById(ids).associateBy { it.id!! }
133+
}
134+
135+
private fun buildDisplayTitle(
136+
room: ChatRoom,
137+
viewerId: Long,
138+
cachedUsers: Map<Long, User>,
139+
): String {
140+
val guideNickname = cachedUsers[room.guideId]?.nickname ?: "Guide-${room.guideId}"
141+
val userNickname = cachedUsers[room.userId]?.nickname ?: "User-${room.userId}"
142+
val counterpartName = if (viewerId == room.guideId) userNickname else guideNickname
143+
return "${counterpartName}님과의 채팅"
144+
}
145+
93146
private fun parseCursor(cursor: String?): Pair<ZonedDateTime?, Long?> {
94147
if (cursor.isNullOrBlank()) {
95148
return null to null

src/test/kotlin/com/back/koreaTravelGuide/domain/userChat/chatroom/controller/ChatRoomControllerTest.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class ChatRoomControllerTest {
125125
.andExpect(status().isOk)
126126
.andExpect(jsonPath("$.data.rooms.length()").value(3))
127127
.andExpect(jsonPath("$.data.rooms[0].id").value(existingRoom.id!!.toInt()))
128+
.andExpect(jsonPath("$.data.rooms[0].displayTitle").value("${guide.nickname}님과의 채팅"))
128129
.andReturn()
129130

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

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

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

173177
@Test

0 commit comments

Comments
 (0)