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:
		@@ -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
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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?
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 보이스온 - 채팅방 메시지 전송 상태 Enum
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package kr.co.vividnext.sodalive.chat.room
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.annotation.Keep
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 메시지 전송 상태
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Keep
 | 
				
			||||||
 | 
					enum class MessageStatus {
 | 
				
			||||||
 | 
					    /** 전송 중 */
 | 
				
			||||||
 | 
					    SENDING,
 | 
				
			||||||
 | 
					    /** 전송 완료 */
 | 
				
			||||||
 | 
					    SENT,
 | 
				
			||||||
 | 
					    /** 전송 실패 */
 | 
				
			||||||
 | 
					    FAILED
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
		Reference in New Issue
	
	Block a user