feat(chat-room): 7.3 로컬 DB 동기화 및 메시지 상태/정리 로직 구현
- ChatMessageDao에 상태 업데이트/정리 보조 쿼리 추가 - ChatRepository에 로컬 저장, 상태 업데이트, 오래된 메시지 정리 API 추가 - Activity 전송/상태 변경 시 DB 반영 및 로딩 후 정리 트리거
This commit is contained in:
		@@ -44,6 +44,8 @@ class ChatRepository(
 | 
			
		||||
                val entities = data.messages.map { it.toEntity(roomId) }
 | 
			
		||||
                Single.fromCallable {
 | 
			
		||||
                    kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) }
 | 
			
		||||
                    // 오래된 메시지 정리(최신 200개 유지)
 | 
			
		||||
                    kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, 200) }
 | 
			
		||||
                    data
 | 
			
		||||
                }.subscribeOn(Schedulers.io())
 | 
			
		||||
            }
 | 
			
		||||
@@ -60,11 +62,50 @@ class ChatRepository(
 | 
			
		||||
                val entities = data.messages.map { it.toEntity(roomId) }
 | 
			
		||||
                Single.fromCallable {
 | 
			
		||||
                    kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) }
 | 
			
		||||
                    // 오래된 메시지 정리(최신 200개 유지)
 | 
			
		||||
                    kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, 200) }
 | 
			
		||||
                    data
 | 
			
		||||
                }.subscribeOn(Schedulers.io())
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 로컬 임시 메시지 저장(SENDING 등)
 | 
			
		||||
     */
 | 
			
		||||
    fun saveLocalMessage(roomId: Long, message: ChatMessage): Completable {
 | 
			
		||||
        return Completable.fromAction {
 | 
			
		||||
            kotlinx.coroutines.runBlocking { chatDao.insertMessage(message.toEntity(roomId)) }
 | 
			
		||||
        }.subscribeOn(Schedulers.io())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * localId 기반 상태 업데이트(SENDING→SENT/FAILED)
 | 
			
		||||
     */
 | 
			
		||||
    fun updateMessageStatusByLocalId(roomId: Long, localId: String, newStatus: MessageStatus): Completable {
 | 
			
		||||
        return Completable.fromAction {
 | 
			
		||||
            // enum을 문자열로 변환하여 쿼리 파라미터로 전달
 | 
			
		||||
            kotlinx.coroutines.runBlocking { chatDao.updateStatusByLocalId(roomId, localId, newStatus.name) }
 | 
			
		||||
        }.subscribeOn(Schedulers.io())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 오래된 메시지 정리(방별 최신 keepLatest개 유지)
 | 
			
		||||
     */
 | 
			
		||||
    fun trimOldMessages(roomId: Long, keepLatest: Int = 200): Completable {
 | 
			
		||||
        return Completable.fromAction {
 | 
			
		||||
            kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, keepLatest) }
 | 
			
		||||
        }.subscribeOn(Schedulers.io())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun trimOldMessagesInternal(roomId: Long, keepLatest: Int) {
 | 
			
		||||
        if (keepLatest <= 0) return
 | 
			
		||||
        val offset = keepLatest - 1
 | 
			
		||||
        val cutoff = chatDao.getNthLatestCreatedAt(roomId, offset)
 | 
			
		||||
        if (cutoff != null) {
 | 
			
		||||
            chatDao.deleteOldMessages(roomId, cutoff)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 로그아웃 시 모든 로컬 메시지 삭제
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -270,6 +270,11 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
            isGrouped = false
 | 
			
		||||
        )
 | 
			
		||||
        appendMessage(ChatListItem.UserMessage(userMsg))
 | 
			
		||||
        // DB에도 임시 메시지 저장(7.3)
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            chatRepository.saveLocalMessage(roomId, userMsg)
 | 
			
		||||
                .subscribe({ }, { /* 로컬 저장 실패는 UI 진행에 영향 주지 않음 */ })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        // 2) 타이핑 인디케이터 표시
 | 
			
		||||
        chatAdapter.showTypingIndicator()
 | 
			
		||||
@@ -301,6 +306,14 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
            items[index] = ChatListItem.UserMessage(updated)
 | 
			
		||||
            chatAdapter.setItems(items)
 | 
			
		||||
            autoScrollIfAtBottom()
 | 
			
		||||
 | 
			
		||||
            // DB에도 상태 업데이트 반영(7.3)
 | 
			
		||||
            updated.localId?.let { lid ->
 | 
			
		||||
                compositeDisposable.add(
 | 
			
		||||
                    chatRepository.updateMessageStatusByLocalId(roomId, lid, newStatus)
 | 
			
		||||
                        .subscribe({ }, { /* 실패 시 무시(다음 동기화 시 정합성 확보) */ })
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -385,6 +398,12 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
                // 최신 메시지 위치로
 | 
			
		||||
                scrollToBottom()
 | 
			
		||||
                isLoading = false
 | 
			
		||||
 | 
			
		||||
                // 7.3: 오래된 메시지 정리(백그라운드)
 | 
			
		||||
                compositeDisposable.add(
 | 
			
		||||
                    chatRepository.trimOldMessages(roomId, keepLatest = 200)
 | 
			
		||||
                        .subscribe({ }, { /* 무시 */ })
 | 
			
		||||
                )
 | 
			
		||||
            }, { error ->
 | 
			
		||||
                isLoading = false
 | 
			
		||||
                showToast(error.message ?: "채팅방 데이터를 불러오지 못했습니다.")
 | 
			
		||||
@@ -489,6 +508,12 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                isLoading = false
 | 
			
		||||
 | 
			
		||||
                // 7.3: 오래된 메시지 정리(백그라운드)
 | 
			
		||||
                compositeDisposable.add(
 | 
			
		||||
                    chatRepository.trimOldMessages(roomId, keepLatest = 200)
 | 
			
		||||
                        .subscribe({ }, { /* 무시 */ })
 | 
			
		||||
                )
 | 
			
		||||
            }, { error ->
 | 
			
		||||
                isLoading = false
 | 
			
		||||
                showToast(error.message ?: "이전 메시지를 불러오지 못했습니다.")
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,14 @@ interface ChatMessageDao {
 | 
			
		||||
    @Update
 | 
			
		||||
    suspend fun updateMessage(message: ChatMessageEntity)
 | 
			
		||||
 | 
			
		||||
    // 메시지 상태를 localId 기준으로 업데이트(Converter로 문자열 상태 저장)
 | 
			
		||||
    @Query("UPDATE chat_messages SET status = :status WHERE roomId = :roomId AND localId = :localId")
 | 
			
		||||
    suspend fun updateStatusByLocalId(roomId: Long, localId: String, status: String): Int
 | 
			
		||||
 | 
			
		||||
    // N번째 최신(createdAt DESC) 항목의 createdAt (OFFSET 사용)
 | 
			
		||||
    @Query("SELECT createdAt FROM chat_messages WHERE roomId = :roomId ORDER BY createdAt DESC LIMIT 1 OFFSET :offset")
 | 
			
		||||
    suspend fun getNthLatestCreatedAt(roomId: Long, offset: Int): Long?
 | 
			
		||||
 | 
			
		||||
    @Query("DELETE FROM chat_messages WHERE roomId = :roomId AND createdAt < :cutoffTime")
 | 
			
		||||
    suspend fun deleteOldMessages(roomId: Long, cutoffTime: Long)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user