Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.idle.network.model.chat.ReadMessageRequest
import com.idle.network.model.chat.SendMessageRequest
import com.idle.network.source.ChatDataSource
import com.idle.network.util.MAX_RETRY_ATTEMPTS
import com.idle.network.util.calculateEqualJitter
import com.idle.network.util.calculateBackoffTime
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -41,17 +41,35 @@ class ChatRepositoryImpl @Inject constructor(
UserType.WORKER -> chatDataSource.getWorkerChatRooms()
UserType.CENTER -> chatDataSource.getCenterChatRooms()
}.getOrThrow()
chatRoomsResponse.map {
it.toVO()

val chatRooms = chatRoomsResponse.map {
val chatRoom = it.toVO()

if (!localChatDataSource.isChatRoomExist(chatRoom.id, userId)) {
localChatDataSource.insertChatRoom(
myId = userId,
chatRoom = ChatRoom(
id = chatRoom.id,
opponentId = chatRoom.opponentId,
lastMessage = chatRoom.lastMessage,
lastMessageTime = chatRoom.lastMessageTime,
unReadMessageCount = chatRoom.unReadMessageCount,
)
)
}
chatRoom
}
chatRooms
}

override suspend fun retrieveChatRoomMessages(
roomId: String,
myId: String,
messageId: String?
): Result<List<ChatMessage>> = runCatching {
localChatDataSource.getMessages(
roomId = roomId,
myId = myId,
lastMessageId = messageId,
)
}
Expand All @@ -63,7 +81,7 @@ class ChatRepositoryImpl @Inject constructor(
messageId: String?,
): Result<List<ChatMessage>> = runCatching {
if (messageId == null ||
!localChatDataSource.isMessageExist(roomId = roomId, messageId = messageId)
!localChatDataSource.isMessageExist(roomId, myId, messageId)
) {
// 웹소켓으로 얻지 못한 메세지가 있을경우 정합성을 위해 서버에서 호출
when (userType) {
Expand All @@ -79,7 +97,7 @@ class ChatRepositoryImpl @Inject constructor(
}.mapCatching { response ->
response.map {
val message = it.toVO()
if (!localChatDataSource.isChatRoomExist(message.roomId)) {
if (!localChatDataSource.isChatRoomExist(message.roomId, myId)) {
localChatDataSource.insertChatRoom(
myId = myId,
chatRoom = ChatRoom(
Expand All @@ -91,20 +109,21 @@ class ChatRepositoryImpl @Inject constructor(
)
)
}
localChatDataSource.insertMessage(message)
localChatDataSource.insertMessage(message, myId)
}
}.getOrThrow()
}

return@runCatching localChatDataSource.getMessages(
roomId = roomId,
myId = myId,
lastMessageId = messageId,
)
}

override suspend fun generateChatRooms(
userType: UserType,
opponentId: String
opponentId: String,
): Result<String> =
runCatching {
when (userType) {
Expand All @@ -115,13 +134,17 @@ class ChatRepositoryImpl @Inject constructor(
}.getOrThrow()
}

override suspend fun subscribeChatMessage(userId: String): Flow<Message> =
override suspend fun subscribeChatMessage(userId: String, userType: UserType): Flow<Message> =
chatDataSource.subscribeChatMessage(userId)
.map {
val message = it.toVO()
when (message) {
is ChatMessage -> {
if (!localChatDataSource.isChatRoomExist(message.roomId)) {
if (!localChatDataSource.isChatRoomExist(
roomId = message.roomId,
myId = userId,
)
) {
localChatDataSource.insertChatRoom(
myId = userId,
chatRoom = ChatRoom(
Expand All @@ -134,21 +157,22 @@ class ChatRepositoryImpl @Inject constructor(
)
}

localChatDataSource.insertMessage(message)
localChatDataSource.insertMessage(message, userId)
}

is ReadMessage -> {
localChatDataSource.readMessages(
roomId = message.chatroomId,
opponentId = userId,
myId = userId,
senderId = message.opponentId,
)
}
}
message
}.retryWhen { cause, attempt ->
if (cause is IOException && attempt < MAX_RETRY_ATTEMPTS) {
connectWebSocket()
delay(calculateEqualJitter(attempt.toInt()))
delay(calculateBackoffTime(attempt.toInt()))
true
} else {
false
Expand All @@ -174,6 +198,7 @@ class ChatRepositoryImpl @Inject constructor(

override suspend fun readMessage(
chatroomId: String,
myId: String,
opponentId: String,
userType: UserType,
): Result<Unit> =
Expand All @@ -186,7 +211,8 @@ class ChatRepositoryImpl @Inject constructor(
).onSuccess {
localChatDataSource.readMessages(
roomId = chatroomId,
opponentId = opponentId,
myId = myId,
senderId = opponentId,
)
}
}
34 changes: 30 additions & 4 deletions core/database/src/main/java/com/idle/database/dao/ChatRoomsDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,56 @@ import androidx.room.Query
import androidx.room.Transaction
import com.idle.database.model.ChatRoomEntity
import com.idle.database.model.ChatRoomWithMessages
import com.idle.database.model.MessageEntity

@Dao
interface ChatRoomsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertChatRoom(chatRoom: ChatRoomEntity)

@Transaction
@Query(
"""
SELECT * FROM chatRoom
SELECT *
FROM chatRoom
WHERE myId = :userId
ORDER BY id ASC
"""
)
suspend fun getChatRoomsWithMessages(userId: String): List<ChatRoomWithMessages>
suspend fun getChatRooms(userId: String): List<ChatRoomEntity>

@Query(
"""
SELECT *
FROM message
WHERE myId = :userId
ORDER BY createdAt ASC
"""
)
suspend fun getUserMessages(userId: String): List<MessageEntity>

@Transaction
suspend fun getChatRoomsWithMessages(userId: String): List<ChatRoomWithMessages> {
val rooms = getChatRooms(userId)
val messages = getUserMessages(userId)
.groupBy { it.roomId }

return rooms.map { room ->
ChatRoomWithMessages(
chatRoom = room,
messages = messages[room.id] ?: emptyList()
)
}
}

@Query(
"""
SELECT EXISTS(
SELECT 1
FROM chatRoom
WHERE id = :roomId
AND myId = :myId
)
"""
)
suspend fun isChatRoomExist(roomId: String): Boolean
suspend fun isChatRoomExist(roomId: String, myId: String): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ interface MessagesDao {
"""
SELECT * FROM message
WHERE roomId = :roomId
AND myId = :myId
AND (:lastMessageId IS NULL OR id < :lastMessageId)
ORDER BY id DESC
LIMIT :limit
"""
)
suspend fun getMessages(
roomId: String,
myId: String,
lastMessageId: String?,
limit: Int = 50
): List<MessageEntity>
Expand All @@ -31,12 +33,14 @@ interface MessagesDao {
UPDATE message
SET isRead = 1
WHERE roomId = :roomId
AND myId = :myId
AND senderId = :opponentId
AND isRead = 0
"""
)
suspend fun readMessages(
roomId: String,
myId: String,
opponentId: String,
)

Expand All @@ -46,11 +50,13 @@ interface MessagesDao {
SELECT 1
FROM message
WHERE roomId = :roomId
AND myId = :myId
AND id = :messageId
)
"""
)
suspend fun isMessageExist(
myId: String,
roomId: String,
messageId: String,
): Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
package com.idle.database.model

import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.Relation
import com.idle.domain.model.chat.ChatRoom
import java.time.LocalDateTime

@Entity(
tableName = "chatRoom",
indices = [Index(value = ["id"], unique = false)],
primaryKeys = ["id", "myId"],
indices = [Index(value = ["id", "myId"])],
)
data class ChatRoomEntity(
@PrimaryKey
@ColumnInfo(name = "id") val id: String,
val opponentId: String,
val id: String,
val myId: String,
val opponentId: String,
)

data class ChatRoomWithMessages(
@Embedded
val chatRoom: ChatRoomEntity,

@Relation(
parentColumn = "id",
entityColumn = "roomId",
)
val messages: List<MessageEntity>
) {
fun toDomain() = ChatRoom(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.idle.database.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
Expand All @@ -14,15 +13,15 @@ import java.time.LocalDateTime
foreignKeys = arrayOf(
ForeignKey(
entity = ChatRoomEntity::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("roomId"),
parentColumns = ["id", "myId"],
childColumns = ["roomId", "myId"],
)
)
)
data class MessageEntity(
@PrimaryKey
@ColumnInfo(name = "id") val id: String,
@PrimaryKey val id: String,
val roomId: String,
val myId: String,
val senderId: String,
val receiverId: String,
val content: String,
Expand All @@ -40,8 +39,9 @@ data class MessageEntity(
)
}

internal fun ChatMessage.toMessageEntity() = MessageEntity(
internal fun ChatMessage.toMessageEntity(myId: String) = MessageEntity(
id = id,
myId = myId,
roomId = roomId,
senderId = senderId,
receiverId = receiverId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,48 @@ class LocalChatDataSource @Inject constructor(
private val messagesDao: MessagesDao,
private val chatRoomsDao: ChatRoomsDao,
) {
suspend fun insertMessage(message: ChatMessage) =
messagesDao.insertMessage(message.let(ChatMessage::toMessageEntity))
suspend fun insertMessage(message: ChatMessage, myId: String) =
messagesDao.insertMessage(message.toMessageEntity(myId))

suspend fun getMessages(
roomId: String,
myId: String,
lastMessageId: String?
): List<ChatMessage> =
messagesDao.getMessages(
roomId = roomId,
myId = myId,
lastMessageId = lastMessageId,
).map(MessageEntity::toDomain).reversed()

suspend fun readMessages(
roomId: String,
opponentId: String,
): Unit = messagesDao.readMessages(roomId, opponentId)

suspend fun isMessageExist(roomId: String, messageId: String): Boolean =
messagesDao.isMessageExist(roomId, messageId)
myId: String,
senderId: String,
): Unit = messagesDao.readMessages(roomId, myId, senderId)

suspend fun insertChatRoom(myId: String, chatRoom: ChatRoom) = chatRoomsDao.insertChatRoom(
ChatRoomEntity(
id = chatRoom.id,
opponentId = chatRoom.opponentId,
suspend fun isMessageExist(roomId: String, myId: String, messageId: String): Boolean =
messagesDao.isMessageExist(
roomId = roomId,
myId = myId,
messageId = messageId,
)

suspend fun insertChatRoom(myId: String, chatRoom: ChatRoom) =
chatRoomsDao.insertChatRoom(
ChatRoomEntity(
id = chatRoom.id,
opponentId = chatRoom.opponentId,
myId = myId,
)
)
)

suspend fun isChatRoomExist(roomId: String): Boolean = chatRoomsDao.isChatRoomExist(roomId)
suspend fun isChatRoomExist(roomId: String, myId: String): Boolean =
chatRoomsDao.isChatRoomExist(
roomId = roomId,
myId = myId,
)

suspend fun getChatRooms(userId: String): List<ChatRoom> =
chatRoomsDao.getChatRoomsWithMessages(userId)
.map(ChatRoomWithMessages::toDomain)
chatRoomsDao.getChatRoomsWithMessages(userId).map(ChatRoomWithMessages::toDomain)
}
Loading