feat(chat-room): 채팅방 리스트 응답 개선(타입/미리보기/상대 이미지/시간)
- ChatRoomListQueryDto: characterType, lastActivityAt 필드 추가 - ChatRoomListItemDto: opponentType, lastMessagePreview, lastMessageTimeLabel 제공 - 레포지토리 정렬 기준을 최근 메시지 또는 생성일로 일원화(COALESCE)
This commit is contained in:
parent
6cf7dabaef
commit
f2ca013b96
|
@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.chat.room.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.CharacterType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 채팅방 생성 요청 DTO
|
* 채팅방 생성 요청 DTO
|
||||||
|
@ -24,7 +26,9 @@ data class ChatRoomListItemDto(
|
||||||
val chatRoomId: Long,
|
val chatRoomId: Long,
|
||||||
val title: String,
|
val title: String,
|
||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
val lastMessagePreview: String?
|
val opponentType: String,
|
||||||
|
val lastMessagePreview: String?,
|
||||||
|
val lastMessageTimeLabel: String
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,10 +44,13 @@ data class ChatMessageItemDto(
|
||||||
/**
|
/**
|
||||||
* 채팅방 목록 쿼리 DTO (레포지토리 투영용)
|
* 채팅방 목록 쿼리 DTO (레포지토리 투영용)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
data class ChatRoomListQueryDto(
|
data class ChatRoomListQueryDto(
|
||||||
val chatRoomId: Long,
|
val chatRoomId: Long,
|
||||||
val title: String,
|
val title: String,
|
||||||
val imagePath: String?
|
val imagePath: String?,
|
||||||
|
val characterType: CharacterType,
|
||||||
|
val lastActivityAt: LocalDateTime?
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,7 +40,9 @@ interface ChatRoomRepository : JpaRepository<ChatRoom, Long> {
|
||||||
SELECT new kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListQueryDto(
|
SELECT new kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListQueryDto(
|
||||||
r.id,
|
r.id,
|
||||||
r.title,
|
r.title,
|
||||||
pc.character.imagePath
|
pc.character.imagePath,
|
||||||
|
pc.character.characterType,
|
||||||
|
COALESCE(MAX(m.createdAt), r.createdAt)
|
||||||
)
|
)
|
||||||
FROM ChatRoom r
|
FROM ChatRoom r
|
||||||
JOIN r.participants p
|
JOIN r.participants p
|
||||||
|
|
|
@ -199,18 +199,39 @@ class ChatRoomService(
|
||||||
|
|
||||||
val latest = messageRepository.findTopByChatRoomAndIsActiveTrueOrderByCreatedAtDesc(room)
|
val latest = messageRepository.findTopByChatRoomAndIsActiveTrueOrderByCreatedAtDesc(room)
|
||||||
val preview = latest?.message?.let { msg ->
|
val preview = latest?.message?.let { msg ->
|
||||||
if (msg.length <= 25) msg else msg.take(25) + "..."
|
if (msg.length <= 30) msg else msg.take(30) + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val imageUrl = "$imageHost/${q.imagePath ?: "profile/default-profile.png"}"
|
||||||
|
val opponentType = q.characterType.name // Clone or Character
|
||||||
|
val time = latest?.createdAt ?: q.lastActivityAt
|
||||||
|
val timeLabel = formatRelativeTime(time)
|
||||||
|
|
||||||
ChatRoomListItemDto(
|
ChatRoomListItemDto(
|
||||||
chatRoomId = q.chatRoomId,
|
chatRoomId = q.chatRoomId,
|
||||||
title = q.title,
|
title = q.title,
|
||||||
imageUrl = "$imageHost/${q.imagePath ?: "profile/default-profile.png"}",
|
imageUrl = imageUrl,
|
||||||
lastMessagePreview = preview
|
opponentType = opponentType,
|
||||||
|
lastMessagePreview = preview,
|
||||||
|
lastMessageTimeLabel = timeLabel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatRelativeTime(time: java.time.LocalDateTime?): String {
|
||||||
|
if (time == null) return ""
|
||||||
|
val now = java.time.LocalDateTime.now()
|
||||||
|
val duration = java.time.Duration.between(time, now)
|
||||||
|
val seconds = duration.seconds
|
||||||
|
if (seconds <= 60) return "방금"
|
||||||
|
val minutes = duration.toMinutes()
|
||||||
|
if (minutes < 60) return "${'$'}minutes분 전"
|
||||||
|
val hours = duration.toHours()
|
||||||
|
if (hours < 24) return "${'$'}hours시간 전"
|
||||||
|
// 그 외: 날짜 (yyyy-MM-dd)
|
||||||
|
return time.toLocalDate().toString()
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun isMyRoomSessionActive(member: Member, chatRoomId: Long): Boolean {
|
fun isMyRoomSessionActive(member: Member, chatRoomId: Long): Boolean {
|
||||||
val room = chatRoomRepository.findById(chatRoomId).orElseThrow {
|
val room = chatRoomRepository.findById(chatRoomId).orElseThrow {
|
||||||
|
|
Loading…
Reference in New Issue