From 6fd70cec3f636010c5b5851805724e98c3d590b3 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 25 May 2025 19:02:06 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[IDLE-587]=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=EC=97=90=20Sequence=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/ChatRepositoryImpl.kt | 29 +++++++++++++++++-- .../java/com/idle/database/dao/MessagesDao.kt | 28 ++++++++++++++---- .../com/idle/database/model/MessageEntity.kt | 5 +++- .../database/source/LocalChatDataSource.kt | 12 ++++++-- .../com/idle/domain/model/chat/Message.kt | 8 +++-- .../idle/domain/repositorry/ChatRepository.kt | 1 + .../main/java/com/idle/network/api/ChatApi.kt | 6 ++-- .../network/model/chat/ChatMessageResponse.kt | 2 ++ .../model/chat/GetChatMessageResponse.kt | 13 +++++++++ .../network/model/chat/ReadMessageRequest.kt | 1 + .../network/model/chat/ReadMessageResponse.kt | 2 ++ .../com/idle/network/source/ChatDataSource.kt | 5 ++-- .../ChattingDetailViewModel.kt | 1 + 13 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt diff --git a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt index 4197bd70..1cec5b9f 100644 --- a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt +++ b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt @@ -95,7 +95,18 @@ class ChatRepositoryImpl @Inject constructor( messageId = messageId, ) }.mapCatching { response -> - response.map { + val messages = response.chatMessageResponse + val readSequence = response.opponentSequence + messages.lastOrNull()?.let { + localChatDataSource.readMessages( + roomId = roomId, + myId = myId, + senderId = it.senderId ?: return@let, + sequence = readSequence, + ) + } + + messages.map { val message = it.toVO() if (!localChatDataSource.isChatRoomExist(message.roomId, myId)) { localChatDataSource.insertChatRoom( @@ -109,7 +120,15 @@ class ChatRepositoryImpl @Inject constructor( ) ) } - localChatDataSource.insertMessage(message, myId) + + if (!localChatDataSource.hasMessagesAfterSequence( + roomId = message.roomId, + myId = myId, + sequence = message.sequence, + ) + ) { + localChatDataSource.insertMessage(message, myId) + } } }.getOrThrow() } @@ -138,6 +157,7 @@ class ChatRepositoryImpl @Inject constructor( chatDataSource.subscribeChatMessage(userId) .map { val message = it.toVO() + when (message) { is ChatMessage -> { if (!localChatDataSource.isChatRoomExist( @@ -165,9 +185,11 @@ class ChatRepositoryImpl @Inject constructor( roomId = message.chatroomId, myId = userId, senderId = message.opponentId, + sequence = message.sequence, ) } } + message }.retryWhen { cause, attempt -> if (cause is IOException && attempt < MAX_RETRY_ATTEMPTS) { @@ -201,18 +223,21 @@ class ChatRepositoryImpl @Inject constructor( myId: String, opponentId: String, userType: UserType, + sequence: Int, ): Result = chatDataSource.readMessage( userType = userType, readMessageRequest = ReadMessageRequest( chatroomId = chatroomId, opponentId = opponentId, + messageSequence = sequence, ) ).onSuccess { localChatDataSource.readMessages( roomId = chatroomId, myId = myId, senderId = opponentId, + sequence = sequence, ) } } diff --git a/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt b/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt index 4c17927b..e46f4e55 100644 --- a/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt +++ b/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt @@ -1,15 +1,14 @@ package com.idle.database.dao import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Upsert import com.idle.database.model.MessageEntity @Dao interface MessagesDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertMessage(messages: MessageEntity) + @Upsert + suspend fun upsertMessage(messages: MessageEntity) @Query( """ @@ -25,7 +24,7 @@ interface MessagesDao { roomId: String, myId: String, lastMessageId: String?, - limit: Int = 50 + limit: Int = 50, ): List @Query( @@ -36,12 +35,14 @@ interface MessagesDao { AND myId = :myId AND senderId = :opponentId AND isRead = 0 + AND sequence <= :sequence """ ) suspend fun readMessages( roomId: String, myId: String, opponentId: String, + sequence: Int, ) @Query( @@ -60,4 +61,21 @@ interface MessagesDao { roomId: String, messageId: String, ): Boolean + + @Query( + """ + SELECT EXISTS( + SELECT 1 + FROM message + WHERE roomId = :roomId + AND myId = :myId + AND sequence > :sequence + ) + """ + ) + suspend fun hasMessagesAfterSequence( + roomId: String, + myId: String, + sequence: Int + ): Boolean } diff --git a/core/database/src/main/java/com/idle/database/model/MessageEntity.kt b/core/database/src/main/java/com/idle/database/model/MessageEntity.kt index 6dfc2882..18c058cc 100644 --- a/core/database/src/main/java/com/idle/database/model/MessageEntity.kt +++ b/core/database/src/main/java/com/idle/database/model/MessageEntity.kt @@ -9,7 +9,7 @@ import java.time.LocalDateTime @Entity( tableName = "message", - indices = [Index(value = ["roomId", "id"], unique = false)], + indices = [Index(value = ["roomId", "id", "sequence"], unique = true)], foreignKeys = arrayOf( ForeignKey( entity = ChatRoomEntity::class, @@ -27,6 +27,7 @@ data class MessageEntity( val content: String, val createdAt: LocalDateTime, val isRead: Boolean, + val sequence: Int, ) { internal fun toDomain() = ChatMessage( id = id, @@ -36,6 +37,7 @@ data class MessageEntity( content = content, createdAt = createdAt, isRead = isRead, + sequence = sequence, ) } @@ -48,4 +50,5 @@ internal fun ChatMessage.toMessageEntity(myId: String) = MessageEntity( content = content, createdAt = createdAt, isRead = isRead, + sequence = sequence, ) diff --git a/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt b/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt index 244ac3fb..e44f73fc 100644 --- a/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt +++ b/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt @@ -15,7 +15,7 @@ class LocalChatDataSource @Inject constructor( private val chatRoomsDao: ChatRoomsDao, ) { suspend fun insertMessage(message: ChatMessage, myId: String) = - messagesDao.insertMessage(message.toMessageEntity(myId)) + messagesDao.upsertMessage(message.toMessageEntity(myId)) suspend fun getMessages( roomId: String, @@ -32,7 +32,8 @@ class LocalChatDataSource @Inject constructor( roomId: String, myId: String, senderId: String, - ): Unit = messagesDao.readMessages(roomId, myId, senderId) + sequence: Int, + ): Unit = messagesDao.readMessages(roomId, myId, senderId, sequence) suspend fun isMessageExist(roomId: String, myId: String, messageId: String): Boolean = messagesDao.isMessageExist( @@ -41,6 +42,13 @@ class LocalChatDataSource @Inject constructor( messageId = messageId, ) + suspend fun hasMessagesAfterSequence(roomId: String, myId: String, sequence: Int): Boolean = + messagesDao.hasMessagesAfterSequence( + roomId = roomId, + myId = myId, + sequence = sequence, + ) + suspend fun insertChatRoom(myId: String, chatRoom: ChatRoom) = chatRoomsDao.insertChatRoom( ChatRoomEntity( diff --git a/core/domain/src/main/kotlin/com/idle/domain/model/chat/Message.kt b/core/domain/src/main/kotlin/com/idle/domain/model/chat/Message.kt index f7f0b6a3..7e195b5a 100644 --- a/core/domain/src/main/kotlin/com/idle/domain/model/chat/Message.kt +++ b/core/domain/src/main/kotlin/com/idle/domain/model/chat/Message.kt @@ -2,7 +2,7 @@ package com.idle.domain.model.chat import java.time.LocalDateTime -sealed class Message +sealed class Message(open val sequence: Int) data class ChatMessage( val id: String, @@ -12,9 +12,11 @@ data class ChatMessage( val content: String, val createdAt: LocalDateTime, val isRead: Boolean, -) : Message() + override val sequence: Int, +) : Message(sequence) data class ReadMessage( val opponentId: String, val chatroomId: String, -) : Message() + override val sequence: Int, +) : Message(sequence) diff --git a/core/domain/src/main/kotlin/com/idle/domain/repositorry/ChatRepository.kt b/core/domain/src/main/kotlin/com/idle/domain/repositorry/ChatRepository.kt index c3bfdd62..b3e4b85d 100644 --- a/core/domain/src/main/kotlin/com/idle/domain/repositorry/ChatRepository.kt +++ b/core/domain/src/main/kotlin/com/idle/domain/repositorry/ChatRepository.kt @@ -54,5 +54,6 @@ interface ChatRepository { myId: String, opponentId: String, userType: UserType, + sequence: Int, ): Result } diff --git a/core/network/src/main/java/com/idle/network/api/ChatApi.kt b/core/network/src/main/java/com/idle/network/api/ChatApi.kt index 222a5d94..d2690575 100644 --- a/core/network/src/main/java/com/idle/network/api/ChatApi.kt +++ b/core/network/src/main/java/com/idle/network/api/ChatApi.kt @@ -1,7 +1,7 @@ package com.idle.network.api -import com.idle.network.model.chat.ChatMessageResponse import com.idle.network.model.chat.GenerateChatRoomResponse +import com.idle.network.model.chat.GetChatMessageResponse import com.idle.network.model.chat.GetChatRoomResponse import retrofit2.Response import retrofit2.http.GET @@ -26,11 +26,11 @@ interface ChatApi { suspend fun getWorkerChatRoomMessages( @Path("chatroom-id") chatRoomId: String, @Query("message-id") messageId: String?, - ): Response> + ): Response @GET("api/v2/chat/center/chatrooms/{chatroom-id}/messages") suspend fun getCenterChatRoomMessages( @Path("chatroom-id") chatRoomId: String, @Query("message-id") messageId: String?, - ): Response> + ): Response } diff --git a/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt index 505526cf..41be3985 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt @@ -13,6 +13,7 @@ data class ChatMessageResponse( val receiverId: String?, val content: String?, val createdAt: String?, + val messageSequence: Int?, override val type: String = MESSAGE_TYPE, ) : ChatResponse() { override fun toVO() = ChatMessage( @@ -24,6 +25,7 @@ data class ChatMessageResponse( createdAt = createdAt?.let { LocalDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME) } ?: LocalDateTime.MIN, isRead = false, + sequence = messageSequence ?: -1 ) } diff --git a/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt new file mode 100644 index 00000000..414ed508 --- /dev/null +++ b/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt @@ -0,0 +1,13 @@ +package com.idle.network.model.chat + +import com.idle.domain.model.chat.ChatMessage +import kotlinx.serialization.Serializable + +@Serializable +data class GetChatMessageResponse( + val chatMessageResponse: List = emptyList(), + val opponentSequence: Int = 0, +) { + fun toVO(): Pair, Int> = + chatMessageResponse.map(ChatMessageResponse::toVO) to opponentSequence +} diff --git a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt index 545929b6..18b4cdb7 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt @@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable data class ReadMessageRequest( val chatroomId: String, val opponentId: String, + val messageSequence: Int, ) diff --git a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt index 4898faf5..c0872630 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt @@ -7,10 +7,12 @@ import kotlinx.serialization.Serializable data class ReadMessageResponse( val readByUserId: String?, val chatroomId: String?, + val messageSequence: Int?, override val type: String = READ_TYPE, ) : ChatResponse() { override fun toVO() = ReadMessage( opponentId = readByUserId ?: "", chatroomId = chatroomId ?: "", + sequence = messageSequence ?: -1 ) } diff --git a/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt b/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt index 721c1522..6076f262 100644 --- a/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt +++ b/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt @@ -8,6 +8,7 @@ import com.idle.network.di.TokenManager import com.idle.network.model.chat.ChatMessageResponse import com.idle.network.model.chat.ChatResponse import com.idle.network.model.chat.GenerateChatRoomResponse +import com.idle.network.model.chat.GetChatMessageResponse import com.idle.network.model.chat.GetChatRoomResponse import com.idle.network.model.chat.ReadMessageRequest import com.idle.network.model.chat.SendMessageRequest @@ -47,7 +48,7 @@ class ChatDataSource @Inject constructor( suspend fun getWorkerChatRoomMessages( roomId: String, messageId: String?, - ): Result> = + ): Result = safeApiCall { chatApi.getWorkerChatRoomMessages( chatRoomId = roomId, @@ -58,7 +59,7 @@ class ChatDataSource @Inject constructor( suspend fun getCenterChatRoomMessages( roomId: String, messageId: String?, - ): Result> = + ): Result = safeApiCall { chatApi.getCenterChatRoomMessages( chatRoomId = roomId, diff --git a/feature/chatting-detail/src/main/java/com/idle/chatting_detail/ChattingDetailViewModel.kt b/feature/chatting-detail/src/main/java/com/idle/chatting_detail/ChattingDetailViewModel.kt index 4b0561e5..57a677f4 100644 --- a/feature/chatting-detail/src/main/java/com/idle/chatting_detail/ChattingDetailViewModel.kt +++ b/feature/chatting-detail/src/main/java/com/idle/chatting_detail/ChattingDetailViewModel.kt @@ -187,6 +187,7 @@ class ChattingDetailViewModel @Inject constructor( myId = myId, opponentId = opponentId, userType = myUserType, + sequence = _chatMessages.value?.lastOrNull()?.sequence ?: return ).onSuccess { _chatMessages.value = _chatMessages.value?.map { if (it.receiverId == myId) it.copy(isRead = true) else it From 816ddc2bc46022692397bd689dc68119d220a781 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Sun, 25 May 2025 19:46:25 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[IDLE-587]=20DTO=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/idle/data/repository/ChatRepositoryImpl.kt | 4 ++-- .../java/com/idle/network/model/chat/ChatMessageResponse.kt | 4 ++-- .../com/idle/network/model/chat/GetChatMessageResponse.kt | 4 ++-- .../java/com/idle/network/model/chat/ReadMessageRequest.kt | 2 +- .../java/com/idle/network/model/chat/ReadMessageResponse.kt | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt index 1cec5b9f..86b15acd 100644 --- a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt +++ b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt @@ -96,7 +96,7 @@ class ChatRepositoryImpl @Inject constructor( ) }.mapCatching { response -> val messages = response.chatMessageResponse - val readSequence = response.opponentSequence + val readSequence = response.sequence messages.lastOrNull()?.let { localChatDataSource.readMessages( roomId = roomId, @@ -230,7 +230,7 @@ class ChatRepositoryImpl @Inject constructor( readMessageRequest = ReadMessageRequest( chatroomId = chatroomId, opponentId = opponentId, - messageSequence = sequence, + sequence = sequence, ) ).onSuccess { localChatDataSource.readMessages( diff --git a/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt index 41be3985..3cebc789 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/ChatMessageResponse.kt @@ -13,7 +13,7 @@ data class ChatMessageResponse( val receiverId: String?, val content: String?, val createdAt: String?, - val messageSequence: Int?, + val sequence: Int?, override val type: String = MESSAGE_TYPE, ) : ChatResponse() { override fun toVO() = ChatMessage( @@ -25,7 +25,7 @@ data class ChatMessageResponse( createdAt = createdAt?.let { LocalDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME) } ?: LocalDateTime.MIN, isRead = false, - sequence = messageSequence ?: -1 + sequence = sequence ?: -1 ) } diff --git a/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt index 414ed508..d5420de8 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt @@ -6,8 +6,8 @@ import kotlinx.serialization.Serializable @Serializable data class GetChatMessageResponse( val chatMessageResponse: List = emptyList(), - val opponentSequence: Int = 0, + val sequence: Int = 0, ) { fun toVO(): Pair, Int> = - chatMessageResponse.map(ChatMessageResponse::toVO) to opponentSequence + chatMessageResponse.map(ChatMessageResponse::toVO) to sequence } diff --git a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt index 18b4cdb7..1964ab62 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageRequest.kt @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable data class ReadMessageRequest( val chatroomId: String, val opponentId: String, - val messageSequence: Int, + val sequence: Int, ) diff --git a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt index c0872630..44f0af89 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/ReadMessageResponse.kt @@ -7,12 +7,12 @@ import kotlinx.serialization.Serializable data class ReadMessageResponse( val readByUserId: String?, val chatroomId: String?, - val messageSequence: Int?, + val sequence: Int?, override val type: String = READ_TYPE, ) : ChatResponse() { override fun toVO() = ReadMessage( opponentId = readByUserId ?: "", chatroomId = chatroomId ?: "", - sequence = messageSequence ?: -1 + sequence = sequence ?: -1 ) } From 667ece4cb87b012ee3a2dbbeaeed804545eb4da2 Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 26 May 2025 00:50:16 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[IDLE-587]=20UserType=EC=9D=B4=20Empty?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20DeviceToken=EC=9D=84=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=EB=A5=BC=20=ED=98=B8=EC=B6=9C=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idle/care/notification/NotificationService.kt | 12 +++++++----- .../com/idle/data/repository/ChatRepositoryImpl.kt | 8 +++++++- .../java/com/idle/database/model/MessageEntity.kt | 10 +++++----- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/idle/care/notification/NotificationService.kt b/app/src/main/java/com/idle/care/notification/NotificationService.kt index 7050ccbe..cfd2c78e 100644 --- a/app/src/main/java/com/idle/care/notification/NotificationService.kt +++ b/app/src/main/java/com/idle/care/notification/NotificationService.kt @@ -3,8 +3,8 @@ package com.idle.care.notification import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.idle.domain.model.error.ErrorHelper -import com.idle.domain.repositorry.TokenRepository import com.idle.domain.repositorry.ProfileRepository +import com.idle.domain.repositorry.TokenRepository import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope @@ -40,10 +40,12 @@ class NotificationService : FirebaseMessagingService() { scope.launch { val userType = profileRepository.getMyUserType() - tokenRepository.postDeviceToken( - deviceToken = token, - userType = userType, - ) + if (userType.isNotEmpty()) { + tokenRepository.postDeviceToken( + deviceToken = token, + userType = userType, + ) + } } } diff --git a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt index 86b15acd..6d942536 100644 --- a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt +++ b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.idle.data.repository +import android.util.Log import com.idle.database.source.LocalChatDataSource import com.idle.domain.model.auth.UserType import com.idle.domain.model.chat.ChatMessage @@ -108,6 +109,9 @@ class ChatRepositoryImpl @Inject constructor( messages.map { val message = it.toVO() + + Log.d("test1", message.toString()) + if (!localChatDataSource.isChatRoomExist(message.roomId, myId)) { localChatDataSource.insertChatRoom( myId = myId, @@ -121,8 +125,10 @@ class ChatRepositoryImpl @Inject constructor( ) } + Log.d("test2", message.toString()) + if (!localChatDataSource.hasMessagesAfterSequence( - roomId = message.roomId, + roomId = roomId, myId = myId, sequence = message.sequence, ) diff --git a/core/database/src/main/java/com/idle/database/model/MessageEntity.kt b/core/database/src/main/java/com/idle/database/model/MessageEntity.kt index 18c058cc..2e7f40fd 100644 --- a/core/database/src/main/java/com/idle/database/model/MessageEntity.kt +++ b/core/database/src/main/java/com/idle/database/model/MessageEntity.kt @@ -3,23 +3,23 @@ package com.idle.database.model import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index -import androidx.room.PrimaryKey import com.idle.domain.model.chat.ChatMessage import java.time.LocalDateTime @Entity( tableName = "message", - indices = [Index(value = ["roomId", "id", "sequence"], unique = true)], - foreignKeys = arrayOf( + primaryKeys = ["myId", "id"], + indices = [Index(value = ["roomId", "sequence"])], + foreignKeys = [ ForeignKey( entity = ChatRoomEntity::class, parentColumns = ["id", "myId"], childColumns = ["roomId", "myId"], ) - ) + ] ) data class MessageEntity( - @PrimaryKey val id: String, + val id: String, val roomId: String, val myId: String, val senderId: String, From cdff4039af2625f887740ad0d6bdc79170353dbc Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 26 May 2025 02:55:22 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[IDLE-587]=20=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=EA=B0=80=20Local=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EB=90=9C=20SeqNumber=EB=B3=B4=EB=8B=A4=20?= =?UTF-8?q?=ED=81=B4=20=EB=95=8C=EB=A7=8C=20=EB=A1=9C=EC=BB=AC=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idle/data/repository/ChatRepositoryImpl.kt | 17 +++++------------ .../java/com/idle/database/dao/MessagesDao.kt | 13 +++---------- .../idle/database/source/LocalChatDataSource.kt | 5 ++--- .../model/chat/GetChatMessageResponse.kt | 4 ++-- .../com/idle/network/source/ChatDataSource.kt | 6 +++++- 5 files changed, 17 insertions(+), 28 deletions(-) diff --git a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt index 6d942536..17cf9b3e 100644 --- a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt +++ b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.idle.data.repository -import android.util.Log import com.idle.database.source.LocalChatDataSource import com.idle.domain.model.auth.UserType import com.idle.domain.model.chat.ChatMessage @@ -96,8 +95,9 @@ class ChatRepositoryImpl @Inject constructor( messageId = messageId, ) }.mapCatching { response -> - val messages = response.chatMessageResponse + val messages = response.chatMessageInfos val readSequence = response.sequence + messages.lastOrNull()?.let { localChatDataSource.readMessages( roomId = roomId, @@ -110,8 +110,6 @@ class ChatRepositoryImpl @Inject constructor( messages.map { val message = it.toVO() - Log.d("test1", message.toString()) - if (!localChatDataSource.isChatRoomExist(message.roomId, myId)) { localChatDataSource.insertChatRoom( myId = myId, @@ -125,14 +123,9 @@ class ChatRepositoryImpl @Inject constructor( ) } - Log.d("test2", message.toString()) - - if (!localChatDataSource.hasMessagesAfterSequence( - roomId = roomId, - myId = myId, - sequence = message.sequence, - ) - ) { + val maxSeq = + localChatDataSource.getMaxLocalSequence(roomId, myId) ?: Int.MIN_VALUE + if (message.sequence > maxSeq) { localChatDataSource.insertMessage(message, myId) } } diff --git a/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt b/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt index e46f4e55..6679f7d0 100644 --- a/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt +++ b/core/database/src/main/java/com/idle/database/dao/MessagesDao.kt @@ -64,18 +64,11 @@ interface MessagesDao { @Query( """ - SELECT EXISTS( - SELECT 1 + SELECT MAX(sequence) FROM message WHERE roomId = :roomId - AND myId = :myId - AND sequence > :sequence - ) + AND myId = :myId """ ) - suspend fun hasMessagesAfterSequence( - roomId: String, - myId: String, - sequence: Int - ): Boolean + suspend fun getMaxLocalSequence(roomId: String, myId: String): Int? } diff --git a/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt b/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt index e44f73fc..d5628223 100644 --- a/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt +++ b/core/database/src/main/java/com/idle/database/source/LocalChatDataSource.kt @@ -42,11 +42,10 @@ class LocalChatDataSource @Inject constructor( messageId = messageId, ) - suspend fun hasMessagesAfterSequence(roomId: String, myId: String, sequence: Int): Boolean = - messagesDao.hasMessagesAfterSequence( + suspend fun getMaxLocalSequence(roomId: String, myId: String): Int? = + messagesDao.getMaxLocalSequence( roomId = roomId, myId = myId, - sequence = sequence, ) suspend fun insertChatRoom(myId: String, chatRoom: ChatRoom) = diff --git a/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt b/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt index d5420de8..2f452d59 100644 --- a/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt +++ b/core/network/src/main/java/com/idle/network/model/chat/GetChatMessageResponse.kt @@ -5,9 +5,9 @@ import kotlinx.serialization.Serializable @Serializable data class GetChatMessageResponse( - val chatMessageResponse: List = emptyList(), + val chatMessageInfos: List = emptyList(), val sequence: Int = 0, ) { fun toVO(): Pair, Int> = - chatMessageResponse.map(ChatMessageResponse::toVO) to sequence + chatMessageInfos.map(ChatMessageResponse::toVO) to sequence } diff --git a/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt b/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt index 6076f262..acfad3f4 100644 --- a/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt +++ b/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt @@ -122,17 +122,21 @@ class ChatDataSource @Inject constructor( runCatching { Log.d("test", "/pub/send/${userType.apiValue.lowercase()}") - session?.convertAndSend( + val result = session?.convertAndSend( headers = StompSendHeaders(destination = "/pub/send/${userType.apiValue.lowercase()}"), body = sendMessageRequest, serializer = SendMessageRequest.serializer(), ) + + Log.d("test", result.toString()) } suspend fun readMessage( userType: UserType, readMessageRequest: ReadMessageRequest ): Result = runCatching { + Log.d("test", "/pub/read/${userType.apiValue.lowercase()}") + session?.convertAndSend( headers = StompSendHeaders(destination = "/pub/read/${userType.apiValue.lowercase()}"), body = readMessageRequest, From 64f7f275ba3b0723b2edbaed8f45ac5c6c1107eb Mon Sep 17 00:00:00 2001 From: tgyuuAn Date: Mon, 26 May 2025 04:01:58 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[IDLE-587]=20=EC=84=9C=EB=B2=84=20<->=20?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EC=83=81?= =?UTF-8?q?=ED=98=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/ChatRepositoryImpl.kt | 40 ++++++++++--------- .../com/idle/network/source/ChatDataSource.kt | 11 ----- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt index 17cf9b3e..21962d0a 100644 --- a/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt +++ b/core/data/src/main/java/com/idle/data/repository/ChatRepositoryImpl.kt @@ -107,28 +107,30 @@ class ChatRepositoryImpl @Inject constructor( ) } - messages.map { - val message = it.toVO() - - if (!localChatDataSource.isChatRoomExist(message.roomId, myId)) { - localChatDataSource.insertChatRoom( - myId = myId, - chatRoom = ChatRoom( - id = message.roomId, - opponentId = if (myId == message.senderId) message.receiverId else message.senderId, - lastMessage = message.content, - lastMessageTime = message.createdAt, - unReadMessageCount = 1, + messages + .sortedBy { it.sequence } + .map { + val message = it.toVO() + + if (!localChatDataSource.isChatRoomExist(message.roomId, myId)) { + localChatDataSource.insertChatRoom( + myId = myId, + chatRoom = ChatRoom( + id = message.roomId, + opponentId = if (myId == message.senderId) message.receiverId else message.senderId, + lastMessage = message.content, + lastMessageTime = message.createdAt, + unReadMessageCount = 1, + ) ) - ) - } + } - val maxSeq = - localChatDataSource.getMaxLocalSequence(roomId, myId) ?: Int.MIN_VALUE - if (message.sequence > maxSeq) { - localChatDataSource.insertMessage(message, myId) + val maxSeq = + localChatDataSource.getMaxLocalSequence(roomId, myId) ?: Int.MIN_VALUE + if (message.sequence > maxSeq) { + localChatDataSource.insertMessage(message, myId) + } } - } }.getOrThrow() } diff --git a/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt b/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt index acfad3f4..ee47b040 100644 --- a/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt +++ b/core/network/src/main/java/com/idle/network/source/ChatDataSource.kt @@ -1,11 +1,9 @@ package com.idle.network.source -import android.util.Log import com.idle.domain.model.auth.UserType import com.idle.network.BuildConfig import com.idle.network.api.ChatApi import com.idle.network.di.TokenManager -import com.idle.network.model.chat.ChatMessageResponse import com.idle.network.model.chat.ChatResponse import com.idle.network.model.chat.GenerateChatRoomResponse import com.idle.network.model.chat.GetChatMessageResponse @@ -93,7 +91,6 @@ class ChatDataSource @Inject constructor( connectionAttempts++ connectWebSocket().getOrThrow() } else { - Log.d("test connect", throwable.stackTraceToString()) throw throwable } } @@ -102,7 +99,6 @@ class ChatDataSource @Inject constructor( session?.disconnect() Result.success(Unit) } catch (e: Exception) { - Log.d("test disconnect", e.stackTraceToString()) Result.failure(e) } @@ -111,7 +107,6 @@ class ChatDataSource @Inject constructor( StompSubscribeHeaders(destination = "/sub/${userId}"), chatResponseSerializer, )?.map { - Log.d("test", it.toString()) it } ?: flow { throw IOException("웹소켓을 먼저 연결해주세요.") } @@ -120,23 +115,17 @@ class ChatDataSource @Inject constructor( sendMessageRequest: SendMessageRequest ): Result = runCatching { - Log.d("test", "/pub/send/${userType.apiValue.lowercase()}") - val result = session?.convertAndSend( headers = StompSendHeaders(destination = "/pub/send/${userType.apiValue.lowercase()}"), body = sendMessageRequest, serializer = SendMessageRequest.serializer(), ) - - Log.d("test", result.toString()) } suspend fun readMessage( userType: UserType, readMessageRequest: ReadMessageRequest ): Result = runCatching { - Log.d("test", "/pub/read/${userType.apiValue.lowercase()}") - session?.convertAndSend( headers = StompSendHeaders(destination = "/pub/read/${userType.apiValue.lowercase()}"), body = readMessageRequest,