feat(character): 홈의 최근 목록을 채팅방 기반으로 노출

- ChatRoomService.listMyChatRooms 사용, 최근 순 최대 10개 노출
- 방 title/imageUrl을 그대로 사용해 UI/데이터 일관성 유지
- 비로그인 사용자는 빈 배열 반환

refactor(dto): RecentCharacter.characterId → roomId로 변경
This commit is contained in:
Klaus 2025-08-14 00:53:35 +09:00
parent e6d63592ec
commit 6cf7dabaef
5 changed files with 19 additions and 51 deletions

View File

@ -11,6 +11,7 @@ import kr.co.vividnext.sodalive.chat.character.dto.OtherCharacter
import kr.co.vividnext.sodalive.chat.character.dto.RecentCharacter import kr.co.vividnext.sodalive.chat.character.dto.RecentCharacter
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterBannerService
import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
import kr.co.vividnext.sodalive.chat.room.service.ChatRoomService
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.RestController
class ChatCharacterController( class ChatCharacterController(
private val service: ChatCharacterService, private val service: ChatCharacterService,
private val bannerService: ChatCharacterBannerService, private val bannerService: ChatCharacterBannerService,
private val chatRoomService: ChatRoomService,
@Value("\${cloud.aws.cloud-front.host}") @Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String private val imageHost: String
@ -45,15 +47,19 @@ class ChatCharacterController(
) )
} }
// 최근 대화한 캐릭터 조회 (회원별 최근 순으로 최대 10개) // 최근 대화한 캐릭터(채팅방) 조회 (회원별 최근 순으로 최대 10개)
val recentCharacters = service.getRecentCharacters(member, 10) val recentCharacters = if (member == null || member.auth == null) {
.map { emptyList()
RecentCharacter( } else {
characterId = it.id!!, chatRoomService.listMyChatRooms(member, 0, 10)
name = it.name, .map { room ->
imageUrl = "$imageHost/${it.imagePath ?: "profile/default-profile.png"}" RecentCharacter(
) roomId = room.chatRoomId,
} name = room.title,
imageUrl = room.imageUrl
)
}
}
// 인기 캐릭터 조회 (현재는 빈 리스트) // 인기 캐릭터 조회 (현재는 빈 리스트)
val popularCharacters = service.getPopularCharacters() val popularCharacters = service.getPopularCharacters()

View File

@ -22,7 +22,7 @@ data class Character(
) )
data class RecentCharacter( data class RecentCharacter(
val characterId: Long, val roomId: Long,
val name: String, val name: String,
val imageUrl: String val imageUrl: String
) )

View File

@ -1,7 +1,6 @@
package kr.co.vividnext.sodalive.chat.character.repository package kr.co.vividnext.sodalive.chat.character.repository
import kr.co.vividnext.sodalive.chat.character.ChatCharacter import kr.co.vividnext.sodalive.chat.character.ChatCharacter
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
@ -42,31 +41,6 @@ interface ChatCharacterRepository : JpaRepository<ChatCharacter, Long> {
pageable: Pageable pageable: Pageable
): Page<ChatCharacter> ): Page<ChatCharacter>
/**
* 멤버가 최근에 대화한 캐릭터 목록을 반환 (최신 메시지 시간 기준 내림차순)
*/
@Query(
value = """
SELECT c FROM ChatRoom r
JOIN r.participants pu
JOIN r.participants pc
JOIN pc.character c
LEFT JOIN r.messages m
WHERE pu.member = :member
AND pu.participantType = kr.co.vividnext.sodalive.chat.room.ParticipantType.USER
AND pu.isActive = true
AND pc.participantType = kr.co.vividnext.sodalive.chat.room.ParticipantType.CHARACTER
AND pc.isActive = true
AND r.isActive = true
GROUP BY c.id
ORDER BY MAX(COALESCE(m.createdAt, r.createdAt)) DESC
"""
)
fun findRecentCharactersByMember(
@Param("member") member: Member,
pageable: Pageable
): List<ChatCharacter>
/** /**
* 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외) * 특정 캐릭터와 태그를 공유하는 다른 캐릭터를 무작위로 조회 (현재 캐릭터 제외)
*/ */

View File

@ -13,7 +13,6 @@ import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterHobbyRepo
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterTagRepository import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterTagRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepository import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepository
import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
@ -26,16 +25,6 @@ class ChatCharacterService(
private val hobbyRepository: ChatCharacterHobbyRepository, private val hobbyRepository: ChatCharacterHobbyRepository,
private val goalRepository: ChatCharacterGoalRepository private val goalRepository: ChatCharacterGoalRepository
) { ) {
/**
* 최근에 대화한 캐릭터 목록 조회
*/
@Transactional(readOnly = true)
fun getRecentCharacters(member: Member?, limit: Int = 10): List<ChatCharacter> {
if (member == null) return emptyList()
return chatCharacterRepository.findRecentCharactersByMember(member, PageRequest.of(0, limit))
}
/** /**
* 일주일간 대화가 가장 많은 인기 캐릭터 목록 조회 * 일주일간 대화가 가장 많은 인기 캐릭터 목록 조회
* 현재는 채팅방 구현 전이므로 리스트 반환 * 현재는 채팅방 구현 전이므로 리스트 반환

View File

@ -184,9 +184,8 @@ class ChatRoomService(
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun listMyChatRooms(member: Member, page: Int): List<ChatRoomListItemDto> { fun listMyChatRooms(member: Member, page: Int, size: Int = 20): List<ChatRoomListItemDto> {
// 기본 페이지당 20개 고정 val pageable = PageRequest.of(if (page < 0) 0 else page, size)
val pageable = PageRequest.of(if (page < 0) 0 else page, 20)
val rooms: List<ChatRoomListQueryDto> = chatRoomRepository.findMemberRoomsOrderByLastMessageDesc( val rooms: List<ChatRoomListQueryDto> = chatRoomRepository.findMemberRoomsOrderByLastMessageDesc(
member, member,
pageable pageable
@ -200,7 +199,7 @@ 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.substring(0, 25) + "..." if (msg.length <= 25) msg else msg.take(25) + "..."
} }
ChatRoomListItemDto( ChatRoomListItemDto(