diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index 792ec99b63b..ca311c45d4e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -207,7 +207,8 @@ internal class ConversationMapperImpl( connectionStatus = connectionStatusMapper.fromDaoModel(connectionStatus), expiresAt = null, defederated = userDefederated ?: false, - supportedProtocols = userSupportedProtocols?.map { it.toModel() }?.toSet() + supportedProtocols = userSupportedProtocols?.map { it.toModel() }?.toSet(), + activeOneOnOneConversationId = userActiveOneOnOneConversationId?.toModel() ), legalHoldStatus = LegalHoldStatus.DISABLED, userType = domainUserTypeMapper.fromUserTypeEntity(userType), diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt index ea6423ffbe3..76694a38ca8 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/framework/TestConversation.kt @@ -164,7 +164,8 @@ object TestConversation { archived = false, archivedDateTime = null, verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED, - userSupportedProtocols = null + userSupportedProtocols = null, + userActiveOneOnOneConversationId = null, ) fun one_on_one(convId: ConversationId) = Conversation( @@ -318,7 +319,8 @@ object TestConversation { archived = false, archivedDateTime = null, verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED, - userSupportedProtocols = null + userSupportedProtocols = null, + userActiveOneOnOneConversationId = null, ) val MLS_PROTOCOL_INFO = ProtocolInfo.MLS( diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq index d5986ca2acd..679b54ceb26 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Conversations.sq @@ -195,6 +195,10 @@ CASE (Conversation.type) WHEN 'ONE_ON_ONE' THEN User.qualified_id WHEN 'CONNECTION_PENDING' THEN connection_user.qualified_id END AS otherUserId, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.active_one_on_one_conversation_id + WHEN 'CONNECTION_PENDING' THEN connection_user.active_one_on_one_conversation_id +END AS otherUserActiveConversationId, CASE WHEN ((SELECT id FROM SelfUser LIMIT 1) LIKE (Conversation.creator_id || '@%')) THEN 1 ELSE 0 diff --git a/persistence/src/commonMain/db_user/migrations/63.sqm b/persistence/src/commonMain/db_user/migrations/63.sqm new file mode 100644 index 00000000000..4c364e7dd86 --- /dev/null +++ b/persistence/src/commonMain/db_user/migrations/63.sqm @@ -0,0 +1,109 @@ + +DROP VIEW IF EXISTS ConversationDetails; + +-- Re-create ConversationDetails view now that it has a new "otherUserActiveConversationId" field + +CREATE VIEW IF NOT EXISTS ConversationDetails AS +SELECT +Conversation.qualified_id AS qualifiedId, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.name + WHEN 'CONNECTION_PENDING' THEN connection_user.name + ELSE Conversation.name +END AS name, +Conversation.type, +Call.status AS callStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.preview_asset_id + WHEN 'CONNECTION_PENDING' THEN connection_user.preview_asset_id +END AS previewAssetId, +Conversation.muted_status AS mutedStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.team + ELSE Conversation.team_id +END AS teamId, +CASE (Conversation.type) + WHEN 'CONNECTION_PENDING' THEN Connection.last_update_date + ELSE Conversation.last_modified_date +END AS lastModifiedDate, +Conversation.last_read_date AS lastReadDate, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.user_availability_status + WHEN 'CONNECTION_PENDING' THEN connection_user.user_availability_status +END AS userAvailabilityStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.user_type + WHEN 'CONNECTION_PENDING' THEN connection_user.user_type +END AS userType, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.bot_service + WHEN 'CONNECTION_PENDING' THEN connection_user.bot_service +END AS botService, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.deleted + WHEN 'CONNECTION_PENDING' THEN connection_user.deleted +END AS userDeleted, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.defederated + WHEN 'CONNECTION_PENDING' THEN connection_user.defederated +END AS userDefederated, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.supported_protocols + WHEN 'CONNECTION_PENDING' THEN connection_user.supported_protocols +END AS userSupportedProtocols, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.connection_status + WHEN 'CONNECTION_PENDING' THEN connection_user.connection_status +END AS connectionStatus, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.qualified_id + WHEN 'CONNECTION_PENDING' THEN connection_user.qualified_id +END AS otherUserId, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN User.active_one_on_one_conversation_id + WHEN 'CONNECTION_PENDING' THEN connection_user.active_one_on_one_conversation_id +END AS otherUserActiveConversationId, +CASE + WHEN ((SELECT id FROM SelfUser LIMIT 1) LIKE (Conversation.creator_id || '@%')) THEN 1 + ELSE 0 +END AS isCreator, +CASE (Conversation.type) + WHEN 'ONE_ON_ONE' THEN coalesce(User.active_one_on_one_conversation_id = Conversation.qualified_id, 0) + ELSE 1 +END AS isActive, +Conversation.last_notified_date AS lastNotifiedMessageDate, +memberRole. role AS selfRole, +Conversation.protocol, +Conversation.mls_cipher_suite, +Conversation.mls_epoch, +Conversation.mls_group_id, +Conversation.mls_last_keying_material_update_date, +Conversation.mls_group_state, +Conversation.access_list, +Conversation.access_role_list, +Conversation.team_id, +Conversation.mls_proposal_timer, +Conversation.muted_time, +Conversation.creator_id, +Conversation.last_modified_date, +Conversation.receipt_mode, +Conversation.message_timer, +Conversation.user_message_timer, +Conversation.incomplete_metadata, +Conversation.archived, +Conversation.archived_date_time, +Conversation.verification_status +FROM Conversation +LEFT JOIN Member ON Conversation.qualified_id = Member.conversation + AND Conversation.type IS 'ONE_ON_ONE' + AND Member.user IS NOT (SELECT SelfUser.id FROM SelfUser LIMIT 1) +LEFT JOIN Member AS memberRole ON Conversation.qualified_id = memberRole.conversation + AND memberRole.user IS (SELECT SelfUser.id FROM SelfUser LIMIT 1) +LEFT JOIN User ON User.qualified_id = Member.user +LEFT JOIN Connection ON Connection.qualified_conversation = Conversation.qualified_id + AND (Connection.status = 'SENT' + OR Connection.status = 'PENDING' + OR Connection.status = 'NOT_CONNECTED' + AND Conversation.type IS 'CONNECTION_PENDING') +LEFT JOIN User AS connection_user ON Connection.qualified_to = connection_user.qualified_id +LEFT JOIN Call ON Call.id IS (SELECT id FROM Call WHERE Call.conversation_id = Conversation.qualified_id AND Call.status IS 'STILL_ONGOING' ORDER BY created_at DESC LIMIT 1); diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt index 4390223c244..5db58eec605 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationMapper.kt @@ -68,7 +68,8 @@ internal class ConversationMapper { archived = archived, archivedDateTime = archived_date_time, verificationStatus = verification_status, - userSupportedProtocols = userSupportedProtocols + userSupportedProtocols = userSupportedProtocols, + userActiveOneOnOneConversationId = otherUserActiveConversationId ) } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt index e141e3ad1c4..c6632975d0f 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationViewEntity.kt @@ -19,6 +19,7 @@ package com.wire.kalium.persistence.dao.conversation import com.wire.kalium.persistence.dao.BotIdEntity import com.wire.kalium.persistence.dao.ConnectionEntity +import com.wire.kalium.persistence.dao.ConversationIDEntity import com.wire.kalium.persistence.dao.QualifiedIDEntity import com.wire.kalium.persistence.dao.SupportedProtocolEntity import com.wire.kalium.persistence.dao.UserAvailabilityStatusEntity @@ -68,7 +69,8 @@ data class ConversationViewEntity( val archived: Boolean, val archivedDateTime: Instant?, val verificationStatus: ConversationEntity.VerificationStatus, - val userSupportedProtocols: Set? + val userSupportedProtocols: Set?, + val userActiveOneOnOneConversationId: ConversationIDEntity? ) { val isMember: Boolean get() = selfRole != null diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt index 3d396f0b62f..41471a6418b 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt @@ -82,7 +82,7 @@ class ConversationDAOTest : BaseDatabaseTest() { } @Test - fun givenConversation_ThenConversationCanBeInserted() = runTest { + fun givenConversationIsInserted_whenFetchingById_thenConversationIsReturned() = runTest { conversationDAO.insertConversation(conversationEntity1) insertTeamUserAndMember(team, user1, conversationEntity1.id) val result = conversationDAO.getConversationByQualifiedID(conversationEntity1.id) @@ -1288,7 +1288,8 @@ class ConversationDAOTest : BaseDatabaseTest() { archived = false, archivedDateTime = null, verificationStatus = ConversationEntity.VerificationStatus.NOT_VERIFIED, - userSupportedProtocols = if (type == ConversationEntity.Type.ONE_ON_ONE) userEntity?.supportedProtocols else null + userSupportedProtocols = if (type == ConversationEntity.Type.ONE_ON_ONE) userEntity?.supportedProtocols else null, + userActiveOneOnOneConversationId = null, ) } diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt index 4305a9ded2e..bdd3ba982f5 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserConversationDAOIntegrationTest.kt @@ -25,15 +25,15 @@ import com.wire.kalium.persistence.dao.member.MemberDAO import com.wire.kalium.persistence.dao.member.MemberEntity import com.wire.kalium.persistence.utils.stubs.newConversationEntity import com.wire.kalium.persistence.utils.stubs.newUserEntity -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.assertTrue -@OptIn(ExperimentalCoroutinesApi::class) class UserConversationDAOIntegrationTest : BaseDatabaseTest() { private val user1 = newUserEntity(id = "1") @@ -251,6 +251,29 @@ class UserConversationDAOIntegrationTest : BaseDatabaseTest() { } } + @Test + fun givenActiveOneOnOneWasSetForConversation_whenFetchingConversationView_thenActiveOneOnOneShouldMatch() = runTest { + userDAO.insertUser(user1) + conversationDAO.insertConversation(conversationEntity1) + memberDAO.insertMember(member1, conversationEntity1.id) + + userDAO.updateActiveOneOnOneConversation(user1.id, conversationEntity1.id) + + val result = conversationDAO.getConversationByQualifiedID(conversationEntity1.id) + assertEquals(conversationEntity1.id, result?.userActiveOneOnOneConversationId) + } + + @Test + fun givenActiveOneOnOneWasNotSetForConversation_whenFetchingConversationView_thenActiveOneOnOneShouldBeNull() = runTest { + userDAO.insertUser(user1) + conversationDAO.insertConversation(conversationEntity1) + memberDAO.insertMember(member1, conversationEntity1.id) + + val result = conversationDAO.getConversationByQualifiedID(conversationEntity1.id) + assertNotNull(result) + assertNull(result.userActiveOneOnOneConversationId) + } + private suspend fun createTestConversation(conversationIDEntity: QualifiedIDEntity, members: List) { conversationDAO.insertConversation( newConversationEntity(conversationIDEntity)