Skip to content

Commit

Permalink
PIA-882: Decoupled PIALibrary from presentation and domain layers
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-said-rehouni committed Dec 12, 2023
1 parent 9eca3a8 commit 8ce2fc4
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 167 deletions.
3 changes: 2 additions & 1 deletion PIA VPN-tvOS/Login/CompositionRoot/LoginFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class LoginFactory {
}

private static func makeLoginProvider() -> LoginProviderType {
LoginProvider(accountProvider: Client.providers.accountProvider)
LoginProvider(accountProvider: Client.providers.accountProvider,
userAccountMapper: UserAccountMapper())
}
}
27 changes: 23 additions & 4 deletions PIA VPN-tvOS/Login/Data/LoginProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,32 @@ import Foundation
import PIALibrary

class LoginProvider: LoginProviderType {
private var accountProvider: AccountProvider
private let accountProvider: AccountProvider
private let userAccountMapper: UserAccountMapper

init(accountProvider: AccountProvider) {
init(accountProvider: AccountProvider, userAccountMapper: UserAccountMapper) {
self.accountProvider = accountProvider
self.userAccountMapper = userAccountMapper
}

func login(with request: LoginRequest, _ callback: LibraryCallback<UserAccount>?) {
accountProvider.login(with: request, callback)
func login(with credentials: Credentials, completion: @escaping (Result<UserAccount, Error>) -> Void) {
let pialibraryCredentials = PIALibrary.Credentials(username: credentials.username, password: credentials.password)
let request = LoginRequest(credentials: pialibraryCredentials)

accountProvider.login(with: request) { [weak self] userAccount, error in
guard let self = self else { return }

if let error = error {
completion(.failure(error))
return
}

guard let userAccount = userAccount else {
completion(.failure(ClientError.unexpectedReply))
return
}

completion(.success(userAccountMapper.map(userAccount: userAccount)))
}
}
}
49 changes: 49 additions & 0 deletions PIA VPN-tvOS/Login/Data/UserAccountMapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// UserAccountMapper.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 12/12/23.
// Copyright © 2023 Private Internet Access Inc. All rights reserved.
//

import Foundation
import PIALibrary

class UserAccountMapper {
func map(userAccount: PIALibrary.UserAccount) -> UserAccount {
let credentials = Credentials(username: userAccount.credentials.username,
password: userAccount.credentials.password)

guard let info = userAccount.info else {
return UserAccount(credentials: credentials, info: nil)
}

let accountInfo = AccountInfo(email: info.email,
username: info.username,
plan: Plan.map(plan: info.plan),
productId: info.productId,
isRenewable: info.isRenewable,
isRecurring: info.isRecurring,
expirationDate: info.expirationDate,
canInvite: info.canInvite,
shouldPresentExpirationAlert: info.shouldPresentExpirationAlert,
renewUrl: info.renewUrl)

return UserAccount(credentials: credentials, info: accountInfo)
}
}

extension Plan {
static func map(plan: PIALibrary.Plan) -> Plan {
switch plan {
case .monthly:
return .monthly
case .yearly:
return .yearly
case .trial:
return .trial
case .other:
return .other
}
}
}
46 changes: 46 additions & 0 deletions PIA VPN-tvOS/Login/Domain/Entities/AccountInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// AccountInfo.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 12/12/23.
// Copyright © 2023 Private Internet Access Inc. All rights reserved.
//

import Foundation

enum Plan: String {
case monthly
case yearly
case trial
case other
}

struct AccountInfo {
let email: String?
let username: String
let plan: Plan
let productId: String?
let isRenewable: Bool
let isRecurring: Bool
let expirationDate: Date
let canInvite: Bool

public var isExpired: Bool {
return (expirationDate.timeIntervalSinceNow < 0)
}

public var dateComponentsBeforeExpiration: DateComponents {
return Calendar.current.dateComponents([.day, .hour], from: Date(), to: expirationDate)
}

public let shouldPresentExpirationAlert: Bool
public let renewUrl: URL?

public func humanReadableExpirationDate(usingLocale locale: Locale = Locale.current) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .none
dateFormatter.locale = locale
return dateFormatter.string(from: self.expirationDate)
}
}
19 changes: 19 additions & 0 deletions PIA VPN-tvOS/Login/Domain/Entities/Credentials.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Credentials.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 12/12/23.
// Copyright © 2023 Private Internet Access Inc. All rights reserved.
//

import Foundation

struct Credentials {
let username: String
let password: String

init(username: String, password: String) {
self.username = username
self.password = password
}
}
23 changes: 23 additions & 0 deletions PIA VPN-tvOS/Login/Domain/Entities/UserAccount.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// UserAccount.swift
// PIA VPN-tvOS
//
// Created by Said Rehouni on 12/12/23.
// Copyright © 2023 Private Internet Access Inc. All rights reserved.
//

import Foundation

struct UserAccount {
let credentials: Credentials
let info: AccountInfo?

var isRenewable: Bool {
return info?.isRenewable ?? false
}

init(credentials: Credentials, info: AccountInfo?) {
self.credentials = credentials
self.info = info
}
}
3 changes: 1 addition & 2 deletions PIA VPN-tvOS/Login/Domain/Interfaces/LoginProviderType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
//

import Foundation
import PIALibrary

protocol LoginProviderType {
func login(with request: LoginRequest, _ callback: LibraryCallback<UserAccount>?)
func login(with credentials: Credentials, completion: @escaping (Result<UserAccount, Error>) -> Void)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import Foundation
import PIALibrary

protocol LoginWithCredentialsUseCaseType {
func execute(username: String, password: String, completion: @escaping (Result<UserAccount, LoginError>) -> Void)
Expand All @@ -25,22 +24,16 @@ class LoginWithCredentialsUseCase: LoginWithCredentialsUseCaseType {
func execute(username: String, password: String, completion: @escaping (Result<UserAccount, LoginError>) -> Void) {
let credentials = Credentials(username: username,
password: password)
let request = LoginRequest(credentials: credentials)

loginProvider.login(with: request) { [weak self] userAccount, error in
loginProvider.login(with: credentials) { [weak self] result in
guard let self = self else { return }

if let error = error {
completion(.failure(errorMapper.map(error: error)))
return
switch result {
case .success(let userAccount):
completion(.success(userAccount))
case .failure(let error):
completion(.failure(errorMapper.map(error: error)))
}

guard let userAccount = userAccount else {
completion(.failure(.generic(message: nil)))
return
}

completion(.success(userAccount))
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions PIA VPN-tvOSTests/Login/Helpers/AccountProviderMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// AccountProviderMock.swift
// PIA VPN-tvOSTests
//
// Created by Said Rehouni on 12/12/23.
// Copyright © 2023 Private Internet Access Inc. All rights reserved.
//

import Foundation
import PIALibrary

class AccountProviderMock: AccountProvider {
var planProducts: [PIALibrary.Plan : PIALibrary.InAppProduct]?
var shouldCleanAccount: Bool = true
var isLoggedIn: Bool = true
var currentUser: PIALibrary.UserAccount?
var oldToken: String?
var apiToken: String?
var vpnToken: String?
var vpnTokenUsername: String?
var vpnTokenPassword: String?
var publicUsername: String?
var currentPasswordReference: Data?
var lastSignupRequest: PIALibrary.SignupRequest?
func migrateOldTokenIfNeeded(_ callback: PIALibrary.SuccessLibraryCallback?) {}

private let userResult: PIALibrary.UserAccount?
private let errorResult: Error?

init(userResult: PIALibrary.UserAccount?, errorResult: Error?) {
self.userResult = userResult
self.errorResult = errorResult
}

func login(with request: PIALibrary.LoginRequest, _ callback: PIALibrary.LibraryCallback<PIALibrary.UserAccount>?) {
callback?(userResult, errorResult)
}

func login(with receiptRequest: PIALibrary.LoginReceiptRequest, _ callback: PIALibrary.LibraryCallback<PIALibrary.UserAccount>?) {}
func login(with linkToken: String, _ callback: ((PIALibrary.UserAccount?, Error?) -> Void)?) {}
func refreshAccountInfo(_ callback: PIALibrary.LibraryCallback<PIALibrary.AccountInfo>?) {}
func accountInformation(_ callback: ((PIALibrary.AccountInfo?, Error?) -> Void)?) {}
func update(with request: PIALibrary.UpdateAccountRequest, resetPassword reset: Bool, andPassword password: String, _ callback: PIALibrary.LibraryCallback<PIALibrary.AccountInfo>?) {}
func logout(_ callback: PIALibrary.SuccessLibraryCallback?) {}
func deleteAccount(_ callback: PIALibrary.SuccessLibraryCallback?) {}
func cleanDatabase() {}
func featureFlags(_ callback: PIALibrary.SuccessLibraryCallback?) {}
func listPlanProducts(_ callback: PIALibrary.LibraryCallback<[PIALibrary.Plan : PIALibrary.InAppProduct]>?) {}
func purchase(plan: PIALibrary.Plan, _ callback: PIALibrary.LibraryCallback<PIALibrary.InAppTransaction>?) {}
func isAPIEndpointAvailable(_ callback: PIALibrary.LibraryCallback<Bool>?) {}
func restorePurchases(_ callback: PIALibrary.SuccessLibraryCallback?) {}
func loginUsingMagicLink(withEmail email: String, _ callback: PIALibrary.SuccessLibraryCallback?) {}
func signup(with request: PIALibrary.SignupRequest, _ callback: PIALibrary.LibraryCallback<PIALibrary.UserAccount>?) {}
func listRenewablePlans(_ callback: PIALibrary.LibraryCallback<[PIALibrary.Plan]>?) {}
func subscriptionInformation(_ callback: PIALibrary.LibraryCallback<PIALibrary.AppStoreInformation>?) {}
func renew(with request: PIALibrary.RenewRequest, _ callback: PIALibrary.LibraryCallback<PIALibrary.UserAccount>?) {}
}
29 changes: 0 additions & 29 deletions PIA VPN-tvOSTests/Login/Helpers/Equatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,3 @@ extension LoginError: Equatable {
}
}
}

extension UserAccount: Equatable {
public static func == (lhs: PIALibrary.UserAccount, rhs: PIALibrary.UserAccount) -> Bool {
lhs.credentials == rhs.credentials
&& lhs.info == rhs.info
}
}

extension Credentials: Equatable {
public static func == (lhs: Credentials, rhs: Credentials) -> Bool {
lhs.username == rhs.username && lhs.password == rhs.password
}
}

extension AccountInfo: Equatable {
public static func == (lhs: AccountInfo, rhs: AccountInfo) -> Bool {
lhs.email == rhs.email
&& lhs.username == rhs.username
&& lhs.plan == rhs.plan
&& lhs.productId == rhs.productId
&& lhs.isRenewable == rhs.isRenewable
&& lhs.isRecurring == rhs.isRecurring
&& lhs.expirationDate == rhs.expirationDate
&& lhs.canInvite == rhs.canInvite
&& lhs.isExpired == rhs.isExpired
&& lhs.shouldPresentExpirationAlert == rhs.shouldPresentExpirationAlert
&& lhs.renewUrl == rhs.renewUrl
}
}
13 changes: 5 additions & 8 deletions PIA VPN-tvOSTests/Login/Helpers/LoginProviderMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@
//

import Foundation
import PIALibrary
@testable import PIA_VPN_tvOS

class LoginProviderMock: LoginProviderType {
private let userResult: UserAccount?
private let errorResult: Error?
private let result: Result<UserAccount, Error>

init(userResult: UserAccount?, errorResult: Error?) {
self.userResult = userResult
self.errorResult = errorResult
init(result: Result<UserAccount, Error>) {
self.result = result
}

func login(with request: LoginRequest, _ callback: LibraryCallback<UserAccount>?) {
callback?(userResult, errorResult)
func login(with credentials: Credentials, completion: @escaping (Result<UserAccount, Error>) -> Void) {
completion(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import Foundation
@testable import PIA_VPN_tvOS
import PIALibrary

class LoginWithCredentialsUseCaseMock: LoginWithCredentialsUseCaseType {
private let result: Result<UserAccount, LoginError>
Expand Down
Loading

0 comments on commit 8ce2fc4

Please sign in to comment.