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

refactor: user legalhold enable event - WPB-10195 #2019

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

jullianm
Copy link
Contributor

@jullianm jullianm commented Oct 11, 2024

TaskWPB-10195 [iOS] Process UserLegalholdEnableEvent

Key points

This PR is part of the quick sync refactoring plan and is related to processing the multiple events we receive from the backend or the push channel.

Specifically, this PR is about implementing UserLegalholdEnableEvent event.

This PR also introduces a new dedicated ClientAPI to fetch data related to clients from backend and a ClientRepository to perform client related storage logic.

⚠️ There was no legacy code for that event, in other words, this event was not processed before. Thus, the implementation is based on what's already been done on Android side with some adaptation to the iOS legacy codebase.

For reference, on Android side, this event is handled in LegalHoldHandlerImpl specifically in this method:
override suspend fun handleEnable(legalHoldEnabled: Event.User.LegalHoldEnabled): Either<CoreFailure, Unit>.

Testing

UTs cover the following use cases, ensuring that

  • self clients are properly fetched from API
  • clients for given users are properly fetched from API
  • a client is properly deleted from storage
  • legalhold enable event is properly processed

Checklist

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

UI accessibility checklist

If your PR includes UI changes, please utilize this checklist:

  • Make sure you use the API for UI elements that support large fonts.
  • All colors are taken from WireDesign.ColorTheme or constructed using WireDesign.BaseColorPalette.
  • New UI elements have Accessibility strings for VoiceOver.

@echoes-hq echoes-hq bot added echoes: maintenance Maintenance activity - Refactoring , Preventive , Improvements to code , Performance improvements echoes/initiative: ios-sync-refactoring labels Oct 11, 2024
Copy link
Contributor

github-actions bot commented Oct 11, 2024

Test Results

166 tests   166 ✅  1s ⏱️
 19 suites    0 💤
  2 files      0 ❌

Results for commit 5d62cda.

♻️ This comment has been updated with latest results.

@jullianm
Copy link
Contributor Author

jullianm commented Oct 11, 2024

Some necessary context here: based on Android implementation, when receiving the LegalholdEnable event, some logic is performed not only for self user but also for other users. Quite similar logic is performed for LegalholdDisable event, again for self user and other users.

However on iOS side, regarding the legacy LegalholdDisable event, we only perform logic for the self user.

While this PR is based on Android implementation and adapted to the existing iOS legacy code, I figured it'd make sense that, when receiving the LegalholdEnable event, we only perform logic for self user as well.

Feel free to share your thoughts on this

///
/// - Parameter httpClient: A http client.

public init(httpClient: any HTTPClient) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought as we go further we would use the APIService here, no?

func getClients(for userIDs: Set<UserID>) async throws -> [UserClients] {
let body = try JSONEncoder.defaultEncoder.encode(UserClientsRequestV0(qualifiedIDs: Array(userIDs)))
let request = HTTPRequest(
path: "/users/list-clients/v2", /// v2 suffix required for api version v0 and v1, suffix removed from next versions
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
path: "/users/list-clients/v2", /// v2 suffix required for api version v0 and v1, suffix removed from next versions
path: "/users/list-clients/v2", // v2 suffix required for api version v0 and v1, suffix removed from next versions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be wrong but I would keep /// for comments documenting functions or params and // for code comments

}

func toAPIModel() -> [UserClients] {
let userClients = payload.reduce(into: [UserClients]()) { partialResult, dict in
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let userClients = payload.reduce(into: [UserClients]()) { partialResult, dict in
let userClients = payload.reduce(into: [UserClients]()) { partialResult, dictionary in

prefer long form

}
}

try context.save()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
try context.save()
if !localUserClient.1 {
try context.save()
}

we only need to save when the client is new

with: id,
in: context
) else {
return WireLogger.userClient.error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error or warn?

in: context
) else {
return WireLogger.userClient.error(
"Failed to find existing client with id: \(id)"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we obfuscate the id or is it missing from WireFondation?

selfUser.selfClient()?.updateSecurityLevelAfterDiscovering(Set([localClient]))
}

try context.save()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save is outside perform here


// sourcery: AutoMockable
/// An API access object for endpoints concerning clients.
public protocol ClientAPI {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with the other APIs:

Suggested change
public protocol ClientAPI {
public protocol UserClientsAPI {

//

// sourcery: AutoMockable
/// An API access object for endpoints concerning clients.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// An API access object for endpoints concerning clients.
/// An API access object for endpoints concerning user clients.

Comment on lines +21 to +23
/// A builder of `ClientAPI`.

public struct ClientAPIBuilder {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A builder of `ClientAPI`.
public struct ClientAPIBuilder {
/// A builder of `UserClientsAPI`.
public struct UserClientsAPIBuilder {

Comment on lines +21 to +23
/// Errors originating from `ClientAPI`.

public enum ClientAPIError: Error {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary now?

}
}

struct ListUserClientV0: Decodable, ToAPIModelConvertible {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? Can't we decode directly as [UserClientV0].self?

Comment on lines +57 to +82
private func processSelfUserClients() async throws {
let remoteSelfClients = try await clientRepository.fetchSelfClients()
let localSelfClients = await context.perform {
let selfUser = userRepository.fetchSelfUser()
return selfUser.clients
}

for remoteSelfClient in remoteSelfClients {
let localUserClient = try await clientRepository.fetchOrCreateClient(
with: remoteSelfClient.id
)

try await clientRepository.updateClient(
with: remoteSelfClient.id,
from: remoteSelfClient,
isNewClient: localUserClient.isNew
)
}

let deletedSelfClientsIDs = localSelfClients.compactMap(\.remoteIdentifier).filter { !remoteSelfClients.map(\.id).contains($0)
}

for deletedSelfClientID in deletedSelfClientsIDs {
await clientRepository.deleteClient(with: deletedSelfClientID)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should all be inside the client repository.

Comment on lines +91 to +93
public func fetchSelfClients() async throws -> [WireAPI.UserClient] {
try await clientAPI.getSelfClients()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer the convention pullSelfClients, which doesn't return it but stores them locally. I don't think the repository should leak api models.

Comment on lines +95 to +97
public func fetchClients(for userIDs: Set<UserID>) async throws -> [WireAPI.UserClients] {
try await clientAPI.getClients(for: userIDs)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment.


/// Errors originating from `ClientRepository`.

enum ClientRepositoryError: Error {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary now?

import Foundation
import WireAPI

extension UUID {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should move this (and the ones in WireAPI) to a test utilities target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
echoes/initiative: ios-sync-refactoring echoes: maintenance Maintenance activity - Refactoring , Preventive , Improvements to code , Performance improvements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants