feat(chat): 채팅방 메시지 조회 API 구현
This commit is contained in:
parent
1509ee0729
commit
002f2c2834
|
@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.PathVariable
|
|||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
|
@ -62,6 +63,24 @@ class ChatRoomController(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 채팅방 메시지 조회 API
|
||||
* - 참여 여부 검증(미참여시 "잘못된 접근입니다")
|
||||
* - messageId가 있으면 해당 ID 이전 20개, 없으면 최신 20개
|
||||
*/
|
||||
@GetMapping("/{chatRoomId}/messages")
|
||||
fun getChatMessages(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
@PathVariable chatRoomId: Long,
|
||||
@RequestParam(required = false) messageId: Long?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.")
|
||||
|
||||
val response = chatRoomService.getChatMessages(member, chatRoomId, messageId)
|
||||
ApiResponse.ok(response)
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 상태 조회 API
|
||||
* - 채팅방 참여 여부 검증
|
||||
|
|
|
@ -24,6 +24,16 @@ data class ChatRoomListItemDto(
|
|||
val lastMessagePreview: String?
|
||||
)
|
||||
|
||||
/**
|
||||
* 채팅방 메시지 아이템 DTO (API 응답용)
|
||||
*/
|
||||
data class ChatMessageItemDto(
|
||||
val messageId: Long,
|
||||
val message: String,
|
||||
val profileImageUrl: String,
|
||||
val mine: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* 채팅방 목록 쿼리 DTO (레포지토리 투영용)
|
||||
*/
|
||||
|
|
|
@ -8,4 +8,11 @@ import org.springframework.stereotype.Repository
|
|||
@Repository
|
||||
interface CharacterChatMessageRepository : JpaRepository<CharacterChatMessage, Long> {
|
||||
fun findTopByChatRoomAndIsActiveTrueOrderByCreatedAtDesc(chatRoom: CharacterChatRoom): CharacterChatMessage?
|
||||
|
||||
fun findTop20ByChatRoomAndIsActiveTrueOrderByIdDesc(chatRoom: CharacterChatRoom): List<CharacterChatMessage>
|
||||
|
||||
fun findTop20ByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(
|
||||
chatRoom: CharacterChatRoom,
|
||||
id: Long
|
||||
): List<CharacterChatMessage>
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService
|
|||
import kr.co.vividnext.sodalive.chat.room.CharacterChatParticipant
|
||||
import kr.co.vividnext.sodalive.chat.room.CharacterChatRoom
|
||||
import kr.co.vividnext.sodalive.chat.room.ParticipantType
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.ChatMessageItemDto
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListItemDto
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.ChatRoomListQueryDto
|
||||
import kr.co.vividnext.sodalive.chat.room.dto.CreateChatRoomResponse
|
||||
|
@ -324,4 +325,36 @@ class ChatRoomService(
|
|||
// 최종 실패 로그 (예외 미전파)
|
||||
log.error("[chat] 외부 세션 종료 최종 실패: sessionId={}, attempts={}", sessionId, maxAttempts)
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun getChatMessages(member: Member, chatRoomId: Long, beforeMessageId: Long?): List<ChatMessageItemDto> {
|
||||
val room = chatRoomRepository.findById(chatRoomId).orElseThrow {
|
||||
SodaException("채팅방을 찾을 수 없습니다.")
|
||||
}
|
||||
val participant = participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
if (participant == null) {
|
||||
throw SodaException("잘못된 접근입니다")
|
||||
}
|
||||
|
||||
val messages = if (beforeMessageId != null) {
|
||||
messageRepository.findTop20ByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(room, beforeMessageId)
|
||||
} else {
|
||||
messageRepository.findTop20ByChatRoomAndIsActiveTrueOrderByIdDesc(room)
|
||||
}
|
||||
|
||||
return messages.map { msg ->
|
||||
val sender = msg.participant
|
||||
val profilePath = when (sender.participantType) {
|
||||
ParticipantType.USER -> sender.member?.profileImage
|
||||
ParticipantType.CHARACTER -> sender.character?.imagePath
|
||||
}
|
||||
val imageUrl = "$imageHost/${profilePath ?: "profile/default-profile.png"}"
|
||||
ChatMessageItemDto(
|
||||
messageId = msg.id!!,
|
||||
message = msg.message,
|
||||
profileImageUrl = imageUrl,
|
||||
mine = sender.member?.id == member.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue