feat(chat-room): 1.1 데이터 모델 생성 및 채팅 메시지 모델 서버-로컬 분리

왜: 서버 스키마와 클라이언트 전용 필드가 혼재되어 혼란을 야기하던 문제를 해결하고, 유지보수성과 확장성을 높이기 위함.

무엇:
- tasks 1.1 수행 (데이터 모델 클래스 생성)
  - ChatMessage 데이터 클래스 생성 (로컬/UI/도메인용)
  - MessageStatus enum 생성 (SENDING, SENT, FAILED)
  - MessageType enum 생성 (USER_MESSAGE, AI_MESSAGE, NOTICE, TYPING_INDICATOR)
  - CharacterType 기존 enum 재사용 (chat/character/detail/CharacterDetailResponse.kt)
  - ChatRoomEnterResponse, ChatMessagesResponse 데이터 클래스 생성
- 채팅 메시지 모델 서버-로컬 분리 및 응답 모델 정리
  - ServerChatMessage DTO 추가 (서버 응답 전용: messageId, message, profileImageUrl, mine, createdAt)
  - ChatMessageMappers 추가: ServerChatMessage.toLocal(isGrouped: Boolean = false)
  - ChatRoomEnterResponse, ChatMessagesResponse에서 messages 타입을 List<ServerChatMessage>로 정리
- 문서
  - docs/chat-room-data-models.md 갱신 (서버/로컬 분리 사항 반영)
  - docs/chat-room-message-model-separation.md 신설 (분리 배경/가이드)

추가 참고:
- 시간 포맷 유틸은 후속 태스크(8.1)에서 테스트와 함께 구현 예정
This commit is contained in:
2025-08-13 05:23:12 +09:00
parent 558f74d861
commit 64deadda0b
8 changed files with 151 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
/*
* 보이스온 - 채팅방 캐릭터 정보 모델
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.chat.character.detail.CharacterType
@Keep
data class CharacterInfo(
@SerializedName("characterId") val characterId: Long,
@SerializedName("name") val name: String,
@SerializedName("profileImageUrl") val profileImageUrl: String,
@SerializedName("characterType") val characterType: CharacterType
)

View File

@@ -0,0 +1,27 @@
/*
* 보이스온 - 채팅 메시지 모델 (서버 응답 기반)
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
/**
* 서버/로컬 공통으로 사용하는 채팅 메시지 모델
* - createdAt: UTC timestamp(ms)
* - status: 기본값 SENT (서버에서 내려오는 메시지)
* - localId: 서버 전송 전 임시 식별자 (전송 중 로컬 식별용)
* - isGrouped: UI 그룹화 처리용 클라이언트 플래그
*/
@Keep
data class ChatMessage(
@SerializedName("messageId") val messageId: Long,
@SerializedName("message") val message: String,
@SerializedName("profileImageUrl") val profileImageUrl: String,
@SerializedName("mine") val mine: Boolean,
@SerializedName("createdAt") val createdAt: Long,
@SerializedName("status") val status: MessageStatus = MessageStatus.SENT,
@SerializedName("localId") val localId: String? = null,
// 서버 필드는 아니지만 UI 로직 편의를 위해 포함 (직렬화 제외 가능)
val isGrouped: Boolean = false
)

View File

@@ -0,0 +1,23 @@
/*
* 보이스온 - 채팅 메시지 매핑 유틸리티
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
/**
* 서버 DTO -> 로컬 표시/도메인 모델 변환
*/
@Keep
fun ServerChatMessage.toLocal(isGrouped: Boolean = false): ChatMessage {
return ChatMessage(
messageId = messageId,
message = message,
profileImageUrl = profileImageUrl,
mine = mine,
createdAt = createdAt,
status = MessageStatus.SENT,
localId = null,
isGrouped = isGrouped
)
}

View File

@@ -0,0 +1,14 @@
/*
* 보이스온 - 점진적 메시지 로딩 응답 모델
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class ChatMessagesResponse(
@SerializedName("messages") val messages: List<ServerChatMessage>,
@SerializedName("hasMore") val hasMore: Boolean,
@SerializedName("nextCursor") val nextCursor: Long?
)

View File

@@ -0,0 +1,15 @@
/*
* 보이스온 - 통합 채팅방 입장 응답 모델
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class ChatRoomEnterResponse(
@SerializedName("roomId") val roomId: Long,
@SerializedName("character") val character: CharacterInfo,
@SerializedName("messages") val messages: List<ServerChatMessage>,
@SerializedName("hasMoreMessages") val hasMoreMessages: Boolean
)

View File

@@ -0,0 +1,19 @@
/*
* 보이스온 - 채팅방 메시지 전송 상태 Enum
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
/**
* 메시지 전송 상태
*/
@Keep
enum class MessageStatus {
/** 전송 중 */
SENDING,
/** 전송 완료 */
SENT,
/** 전송 실패 */
FAILED
}

View File

@@ -0,0 +1,17 @@
/*
* 보이스온 - 채팅방 메시지 타입 Enum
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
/**
* 메시지 표시 타입 (RecyclerView ViewType과 연계)
*/
@Keep
enum class MessageType {
USER_MESSAGE,
AI_MESSAGE,
NOTICE,
TYPING_INDICATOR
}

View File

@@ -0,0 +1,20 @@
/*
* 보이스온 - 채팅 메시지 모델 (서버 응답 전용 DTO)
*/
package kr.co.vividnext.sodalive.chat.room
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
/**
* 서버에서 내려오는 순수 메시지 데이터
* - 클라이언트 전용 필드(status, localId, isGrouped 등)를 포함하지 않음
*/
@Keep
data class ServerChatMessage(
@SerializedName("messageId") val messageId: Long,
@SerializedName("message") val message: String,
@SerializedName("profileImageUrl") val profileImageUrl: String,
@SerializedName("mine") val mine: Boolean,
@SerializedName("createdAt") val createdAt: Long
)