Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle conversation access update event (WPB-10850) 🍒 #2982

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 @@ -84,6 +84,11 @@ interface ConversationMapper {
fun legalHoldStatusFromEntity(legalHoldStatus: ConversationEntity.LegalHoldStatus): Conversation.LegalHoldStatus

fun fromConversationEntityType(type: ConversationEntity.Type): Conversation.Type

fun fromModelToDAOAccess(accessList: Set<Conversation.Access>): List<ConversationEntity.Access>
fun fromModelToDAOAccessRole(accessRoleList: Set<Conversation.AccessRole>): List<ConversationEntity.AccessRole>
fun fromApiModelToAccessModel(accessList: Set<ConversationAccessDTO>): Set<Conversation.Access>
fun fromApiModelToAccessRoleModel(accessRoleList: Set<ConversationAccessRoleDTO>): Set<Conversation.AccessRole>
}

@Suppress("TooManyFunctions", "LongParameterList")
Expand Down Expand Up @@ -460,6 +465,18 @@ internal class ConversationMapperImpl(
override fun fromConversationEntityType(type: ConversationEntity.Type): Conversation.Type {
return type.fromDaoModelToType()
}

override fun fromModelToDAOAccess(accessList: Set<Conversation.Access>): List<ConversationEntity.Access> =
accessList.map { it.toDAO() }

override fun fromModelToDAOAccessRole(accessRoleList: Set<Conversation.AccessRole>): List<ConversationEntity.AccessRole> =
accessRoleList.map { it.toDAO() }

override fun fromApiModelToAccessModel(accessList: Set<ConversationAccessDTO>): Set<Conversation.Access> =
accessList.map { it.toModel() }.toSet()

override fun fromApiModelToAccessRoleModel(accessRoleList: Set<ConversationAccessRoleDTO>): Set<Conversation.AccessRole> =
accessRoleList.map { it.toModel() }.toSet()
}

internal fun ConversationResponse.toConversationType(selfUserTeamId: TeamId?): ConversationEntity.Type {
Expand Down Expand Up @@ -552,6 +569,22 @@ private fun Conversation.Access.toDAO(): ConversationEntity.Access = when (this)
Conversation.Access.CODE -> ConversationEntity.Access.CODE
}

private fun ConversationAccessDTO.toModel(): Conversation.Access = when (this) {
ConversationAccessDTO.PRIVATE -> Conversation.Access.PRIVATE
ConversationAccessDTO.CODE -> Conversation.Access.CODE
ConversationAccessDTO.INVITE -> Conversation.Access.INVITE
ConversationAccessDTO.SELF_INVITE -> Conversation.Access.SELF_INVITE
ConversationAccessDTO.LINK -> Conversation.Access.LINK
}

private fun ConversationAccessRoleDTO.toModel(): Conversation.AccessRole = when (this) {
ConversationAccessRoleDTO.TEAM_MEMBER -> Conversation.AccessRole.TEAM_MEMBER
ConversationAccessRoleDTO.NON_TEAM_MEMBER -> Conversation.AccessRole.NON_TEAM_MEMBER
ConversationAccessRoleDTO.GUEST -> Conversation.AccessRole.GUEST
ConversationAccessRoleDTO.SERVICE -> Conversation.AccessRole.SERVICE
ConversationAccessRoleDTO.EXTERNAL -> Conversation.AccessRole.EXTERNAL
}

internal fun Conversation.Protocol.toApi(): ConvProtocol = when (this) {
Conversation.Protocol.PROTEUS -> ConvProtocol.PROTEUS
Conversation.Protocol.MIXED -> ConvProtocol.MIXED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.wire.kalium.logger.obfuscateDomain
import com.wire.kalium.logger.obfuscateId
import com.wire.kalium.logic.data.client.Client
import com.wire.kalium.logic.data.conversation.ClientId
import com.wire.kalium.logic.data.conversation.Conversation.Access
import com.wire.kalium.logic.data.conversation.Conversation.AccessRole
import com.wire.kalium.logic.data.conversation.Conversation.Member
import com.wire.kalium.logic.data.conversation.Conversation.Protocol
import com.wire.kalium.logic.data.conversation.Conversation.ReceiptMode
Expand Down Expand Up @@ -124,7 +126,8 @@ sealed class Event(open val id: String) {
data class AccessUpdate(
override val id: String,
override val conversationId: ConversationId,
val data: ConversationResponse,
val access: Set<Access>,
val accessRole: Set<AccessRole>,
val qualifiedFrom: UserId,
) : Conversation(id, conversationId) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.wire.kalium.logic.data.client.ClientMapper
import com.wire.kalium.logic.data.connection.ConnectionMapper
import com.wire.kalium.logic.data.conversation.ClientId
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.ConversationMapper
import com.wire.kalium.logic.data.conversation.ConversationRoleMapper
import com.wire.kalium.logic.data.conversation.MemberMapper
import com.wire.kalium.logic.data.conversation.MutedConversationStatus
Expand Down Expand Up @@ -64,7 +65,8 @@ class EventMapper(
private val selfUserId: UserId,
private val receiptModeMapper: ReceiptModeMapper = MapperProvider.receiptModeMapper(),
private val clientMapper: ClientMapper = MapperProvider.clientMapper(),
private val qualifiedIdMapper: QualifiedIdMapper = MapperProvider.qualifiedIdMapper(selfUserId)
private val qualifiedIdMapper: QualifiedIdMapper = MapperProvider.qualifiedIdMapper(selfUserId),
private val conversationMapper: ConversationMapper = MapperProvider.conversationMapper(selfUserId)
) {
fun fromDTO(eventResponse: EventResponse, isLive: Boolean): List<EventEnvelope> {
// TODO(edge-case): Multiple payloads in the same event have the same ID, is this an issue when marking lastProcessedEventId?
Expand Down Expand Up @@ -94,7 +96,7 @@ class EventMapper(
is EventContentDTO.User.LegalHoldDisabledDTO -> legalHoldDisabled(id, eventContentDTO)
is EventContentDTO.FeatureConfig.FeatureConfigUpdatedDTO -> featureConfig(id, eventContentDTO)
is EventContentDTO.Unknown -> unknown(id, eventContentDTO)
is EventContentDTO.Conversation.AccessUpdate -> unknown(id, eventContentDTO)
is EventContentDTO.Conversation.AccessUpdate -> conversationAccessUpdate(id, eventContentDTO)
is EventContentDTO.Conversation.DeletedConversationDTO -> conversationDeleted(id, eventContentDTO)
is EventContentDTO.Conversation.ConversationRenameDTO -> conversationRenamed(id, eventContentDTO)
is EventContentDTO.Team.MemberLeave -> teamMemberLeft(id, eventContentDTO)
Expand Down Expand Up @@ -194,6 +196,17 @@ class EventMapper(
dateTime = eventContentDTO.time
)

private fun conversationAccessUpdate(
id: String,
eventContentDTO: EventContentDTO.Conversation.AccessUpdate
): Event = Event.Conversation.AccessUpdate(
id = id,
conversationId = eventContentDTO.qualifiedConversation.toModel(),
access = conversationMapper.fromApiModelToAccessModel(eventContentDTO.data.access),
accessRole = conversationMapper.fromApiModelToAccessRoleModel(eventContentDTO.data.accessRole),
qualifiedFrom = eventContentDTO.qualifiedFrom.toModel()
)

private fun conversationReceiptModeUpdate(
id: String,
eventContentDTO: EventContentDTO.Conversation.ReceiptModeUpdate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ import com.wire.kalium.logic.sync.receiver.UserPropertiesEventReceiver
import com.wire.kalium.logic.sync.receiver.UserPropertiesEventReceiverImpl
import com.wire.kalium.logic.sync.receiver.asset.AssetMessageHandler
import com.wire.kalium.logic.sync.receiver.asset.AssetMessageHandlerImpl
import com.wire.kalium.logic.sync.receiver.conversation.AccessUpdateEventHandler
import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandler
import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandlerImpl
import com.wire.kalium.logic.sync.receiver.conversation.DeletedConversationEventHandler
Expand Down Expand Up @@ -1466,6 +1467,12 @@ class UserSessionScope internal constructor(
callRepository = callRepository
)

private val conversationAccessUpdateEventHandler: AccessUpdateEventHandler
get() = AccessUpdateEventHandler(
conversationDAO = userStorage.database.conversationDAO,
selfUserId = userId
)

private val conversationEventReceiver: ConversationEventReceiver by lazy {
ConversationEventReceiverImpl(
newMessageHandler,
Expand All @@ -1481,7 +1488,8 @@ class UserSessionScope internal constructor(
conversationCodeUpdateHandler,
conversationCodeDeletedHandler,
typingIndicatorHandler,
protocolUpdateEventHandler
protocolUpdateEventHandler,
conversationAccessUpdateEventHandler
)
}
override val coroutineContext: CoroutineContext = SupervisorJob()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.event.Event
import com.wire.kalium.logic.data.event.EventDeliveryInfo
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.sync.receiver.conversation.AccessUpdateEventHandler
import com.wire.kalium.logic.sync.receiver.conversation.ConversationMessageTimerEventHandler
import com.wire.kalium.logic.sync.receiver.conversation.DeletedConversationEventHandler
import com.wire.kalium.logic.sync.receiver.conversation.MLSWelcomeEventHandler
Expand Down Expand Up @@ -56,7 +57,8 @@ internal class ConversationEventReceiverImpl(
private val codeUpdatedHandler: CodeUpdatedHandler,
private val codeDeletedHandler: CodeDeletedHandler,
private val typingIndicatorHandler: TypingIndicatorHandler,
private val protocolUpdateEventHandler: ProtocolUpdateEventHandler
private val protocolUpdateEventHandler: ProtocolUpdateEventHandler,
private val accessUpdateEventHandler: AccessUpdateEventHandler
) : ConversationEventReceiver {
override suspend fun onEvent(event: Event.Conversation, deliveryInfo: EventDeliveryInfo): Either<CoreFailure, Unit> {
// TODO: Make sure errors are accounted for by each handler.
Expand Down Expand Up @@ -108,11 +110,7 @@ internal class ConversationEventReceiverImpl(
Either.Right(Unit)
}

is Event.Conversation.AccessUpdate -> {
/* no-op */
Either.Right(Unit)
}

is Event.Conversation.AccessUpdate -> accessUpdateEventHandler.handle(event)
is Event.Conversation.ConversationMessageTimer -> conversationMessageTimerEventHandler.handle(event)
is Event.Conversation.CodeDeleted -> codeDeletedHandler.handle(event)
is Event.Conversation.CodeUpdated -> codeUpdatedHandler.handle(event)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.sync.receiver.conversation

import com.wire.kalium.logic.StorageFailure
import com.wire.kalium.logic.data.conversation.ConversationMapper
import com.wire.kalium.logic.data.event.Event
import com.wire.kalium.logic.data.id.toDao
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.wrapStorageRequest
import com.wire.kalium.persistence.dao.conversation.ConversationDAO

interface AccessUpdateEventHandler {
suspend fun handle(event: Event.Conversation.AccessUpdate): Either<StorageFailure, Unit>
}

@Suppress("FunctionNaming")
fun AccessUpdateEventHandler(
selfUserId: UserId,
conversationDAO: ConversationDAO,
conversationMapper: ConversationMapper = MapperProvider.conversationMapper(selfUserId)
) = object : AccessUpdateEventHandler {

override suspend fun handle(event: Event.Conversation.AccessUpdate): Either<StorageFailure, Unit> =
wrapStorageRequest {
conversationDAO.updateAccess(
conversationID = event.conversationId.toDao(),
accessList = conversationMapper.fromModelToDAOAccess(event.access),
accessRoleList = conversationMapper.fromModelToDAOAccessRole(event.accessRole)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,110 @@ class ConversationMapperTest {
assertEquals(ConversationEntity.Type.GROUP, result)
}

@Test
fun givenAccessList_whenMappingFromModelToDAOAccess_thenCorrectValuesShouldBeReturned() {
// given
val accessList = setOf(
Conversation.Access.PRIVATE,
Conversation.Access.CODE,
Conversation.Access.INVITE,
Conversation.Access.LINK,
Conversation.Access.SELF_INVITE
)

val expected = listOf(
ConversationEntity.Access.PRIVATE,
ConversationEntity.Access.CODE,
ConversationEntity.Access.INVITE,
ConversationEntity.Access.LINK,
ConversationEntity.Access.SELF_INVITE
)

// when
val result = conversationMapper.fromModelToDAOAccess(accessList)

// then
assertEquals(expected, result)
}

@Test
fun givenAccessRoleList_whenMappingFromModelToDAOAccessRole_thenCorrectValuesShouldBeReturned() {
// given
val accessRoleList = setOf(
Conversation.AccessRole.SERVICE,
Conversation.AccessRole.GUEST,
Conversation.AccessRole.TEAM_MEMBER,
Conversation.AccessRole.NON_TEAM_MEMBER,
Conversation.AccessRole.EXTERNAL
)

val expected = listOf(
ConversationEntity.AccessRole.SERVICE,
ConversationEntity.AccessRole.GUEST,
ConversationEntity.AccessRole.TEAM_MEMBER,
ConversationEntity.AccessRole.NON_TEAM_MEMBER,
ConversationEntity.AccessRole.EXTERNAL
)

// when
val result = conversationMapper.fromModelToDAOAccessRole(accessRoleList)

// then
assertEquals(expected, result)
}

@Test
fun givenAccessList_whenMappingFromApiModelToAccessModel_thenCorrectValuesShouldBeReturned() {
// given
val accessList = setOf(
ConversationAccessDTO.PRIVATE,
ConversationAccessDTO.CODE,
ConversationAccessDTO.INVITE,
ConversationAccessDTO.LINK,
ConversationAccessDTO.SELF_INVITE
)

val expected = setOf(
Conversation.Access.PRIVATE,
Conversation.Access.CODE,
Conversation.Access.INVITE,
Conversation.Access.LINK,
Conversation.Access.SELF_INVITE
)

// when
val result = conversationMapper.fromApiModelToAccessModel(accessList)

// then
assertEquals(expected, result)
}

@Test
fun givenAccessRoleList_whenMappingFromApiModelToAccessModel_thenCorrectValuesShouldBeReturned() {
// given
val accessRoleList = setOf(
ConversationAccessRoleDTO.SERVICE,
ConversationAccessRoleDTO.GUEST,
ConversationAccessRoleDTO.TEAM_MEMBER,
ConversationAccessRoleDTO.NON_TEAM_MEMBER,
ConversationAccessRoleDTO.EXTERNAL
)

val expected = setOf(
Conversation.AccessRole.SERVICE,
Conversation.AccessRole.GUEST,
Conversation.AccessRole.TEAM_MEMBER,
Conversation.AccessRole.NON_TEAM_MEMBER,
Conversation.AccessRole.EXTERNAL
)

// when
val result = conversationMapper.fromApiModelToAccessRoleModel(accessRoleList)

// then
assertEquals(expected, result)
}

private companion object {
val ORIGINAL_CONVERSATION_ID = ConversationId("original", "oDomain")
val SELF_USER_TEAM_ID = TeamId("teamID")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ object TestEvent {
senderUserId = TestUser.USER_ID
)

fun accessUpdate(eventId: String = "eventId") = Event.Conversation.AccessUpdate(
id = eventId,
conversationId = TestConversation.ID,
access = setOf(Conversation.Access.PRIVATE),
accessRole = setOf(Conversation.AccessRole.TEAM_MEMBER, Conversation.AccessRole.SERVICE),
qualifiedFrom = TestUser.USER_ID
)

fun teamMemberLeave(eventId: String = "eventId") = Event.Team.MemberLeave(
eventId,
teamId = "teamId",
Expand Down Expand Up @@ -200,13 +208,6 @@ object TestEvent {
timestampIso = "2022-03-30T15:36:00.000Z"
)

fun newAccessUpdateEvent() = Event.Conversation.AccessUpdate(
id = "eventId",
conversationId = TestConversation.ID,
data = TestConversation.CONVERSATION_RESPONSE,
qualifiedFrom = TestUser.USER_ID,
)

fun codeUpdated() = Event.Conversation.CodeUpdated(
id = "eventId",
conversationId = TestConversation.ID,
Expand Down
Loading
Loading