2021-04-02 12:13:45 +02:00
//
// A P I S e r v i c e + F o l l o w . s w i f t
// M a s t o d o n
//
// C r e a t e d b y M a i n a s u K C i r n o o n 2 0 2 1 - 4 - 2 .
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import CommonOSLog
import MastodonSDK
extension APIService {
2022-01-27 14:23:39 +01:00
private struct MastodonFollowContext {
let sourceUserID : MastodonUser . ID
let targetUserID : MastodonUser . ID
let isFollowing : Bool
let isPending : Bool
let needsUnfollow : Bool
}
2021-04-02 12:13:45 +02:00
// / T o g g l e f r i e n d s h i p b e t w e e n t a r g e t M a s t o d o n U s e r a n d c u r r e n t M a s t o d o n U s e r
// /
// / F o l l o w i n g / F o l l o w i n g p e n d i n g < - > U n f o l l o w
// /
// / - P a r a m e t e r s :
// / - m a s t o d o n U s e r : t a r g e t M a s t o d o n U s e r
// / - a c t i v e M a s t o d o n A u t h e n t i c a t i o n B o x : ` A u t h e n t i c a t i o n S e r v i c e . M a s t o d o n A u t h e n t i c a t i o n B o x `
// / - R e t u r n s : p u b l i s h e r f o r ` R e l a t i o n s h i p `
func toggleFollow (
2022-01-27 14:23:39 +01:00
user : ManagedObjectRecord < MastodonUser > ,
authenticationBox : MastodonAuthenticationBox
) async throws -> Mastodon . Response . Content < Mastodon . Entity . Relationship > {
let logger = Logger ( subsystem : " APIService " , category : " Follow " )
2021-04-02 12:13:45 +02:00
let managedObjectContext = backgroundManagedObjectContext
2022-01-27 14:23:39 +01:00
let _followContext : MastodonFollowContext ? = try await managedObjectContext . performChanges {
guard let me = authenticationBox . authenticationRecord . object ( in : managedObjectContext ) ? . user else { return nil }
guard let user = user . object ( in : managedObjectContext ) else { return nil }
2021-04-02 12:13:45 +02:00
2022-01-27 14:23:39 +01:00
let isFollowing = user . followingBy . contains ( me )
let isPending = user . followRequestedBy . contains ( me )
let needsUnfollow = isFollowing || isPending
2021-04-02 12:13:45 +02:00
2022-01-27 14:23:39 +01:00
if needsUnfollow {
// u n f o l l o w
user . update ( isFollowing : false , by : me )
user . update ( isFollowRequested : false , by : me )
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Local] update user friendship: undo follow " )
2021-04-02 12:13:45 +02:00
} else {
2022-01-27 14:23:39 +01:00
// f o l l o w
if user . locked {
user . update ( isFollowing : false , by : me )
user . update ( isFollowRequested : true , by : me )
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Local] update user friendship: pending follow " )
2021-04-02 12:13:45 +02:00
} else {
2022-01-27 14:23:39 +01:00
user . update ( isFollowing : true , by : me )
user . update ( isFollowRequested : false , by : me )
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Local] update user friendship: following " )
2021-04-02 12:13:45 +02:00
}
}
2022-01-27 14:23:39 +01:00
let context = MastodonFollowContext (
sourceUserID : me . id ,
targetUserID : user . id ,
isFollowing : isFollowing ,
isPending : isPending ,
needsUnfollow : needsUnfollow
)
return context
2021-04-02 12:13:45 +02:00
}
2022-01-27 14:23:39 +01:00
guard let followContext = _followContext else {
throw APIError . implicit ( . badRequest )
2021-04-02 12:13:45 +02:00
}
2022-01-27 14:23:39 +01:00
// r e q u e s t f o l l o w o r u n f o l l o w
let result : Result < Mastodon . Response . Content < Mastodon . Entity . Relationship > , Error >
do {
let response = try await Mastodon . API . Account . follow (
session : session ,
domain : authenticationBox . domain ,
accountID : followContext . targetUserID ,
followQueryType : followContext . needsUnfollow ? . unfollow : . follow ( query : . init ( ) ) ,
authorization : authenticationBox . userAuthorization
) . singleOutput ( )
result = . success ( response )
} catch {
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Remote] update friendship failure: \( error . localizedDescription ) " )
result = . failure ( error )
}
// u p d a t e f r i e n d s h i p s t a t e
try await managedObjectContext . performChanges {
guard let me = authenticationBox . authenticationRecord . object ( in : managedObjectContext ) ? . user ,
let user = user . object ( in : managedObjectContext )
else { return }
switch result {
case . success ( let response ) :
Persistence . MastodonUser . update (
mastodonUser : user ,
context : Persistence . MastodonUser . RelationshipContext (
entity : response . value ,
me : me ,
networkDate : response . networkDate
)
)
let following = response . value . following
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Remote] update user friendship: following \( following ) " )
case . failure :
// r o l l b a c k
user . update ( isFollowing : followContext . isFollowing , by : me )
user . update ( isFollowRequested : followContext . isPending , by : me )
logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : [Remote] rollback user friendship " )
2021-04-08 10:53:32 +02:00
}
}
2022-01-27 14:23:39 +01:00
let response = try result . get ( )
return response
2021-04-02 12:13:45 +02:00
}
}