mastodon-ios/Mastodon/Service/APIService/APIService+Poll.swift

198 lines
7.7 KiB
Swift

//
// APIService+Poll.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-3-3.
//
import Foundation
import Combine
import CoreData
import CoreDataStack
import CommonOSLog
import DateToolsSwift
import MastodonSDK
extension APIService {
func poll(
domain: String,
pollID: Mastodon.Entity.Poll.ID,
pollObjectID: NSManagedObjectID,
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
let requestMastodonUserID = mastodonAuthenticationBox.userID
return Mastodon.API.Polls.poll(
session: session,
domain: domain,
pollID: pollID,
authorization: authorization
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> in
let entity = response.value
let managedObjectContext = self.backgroundManagedObjectContext
return managedObjectContext.performChanges {
let _requestMastodonUser: MastodonUser? = {
let request = MastodonUser.sortedFetchRequest
request.predicate = MastodonUser.predicate(domain: mastodonAuthenticationBox.domain, id: requestMastodonUserID)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
guard let requestMastodonUser = _requestMastodonUser else {
assertionFailure()
return
}
guard let poll = managedObjectContext.object(with: pollObjectID) as? Poll else { return }
APIService.CoreData.merge(poll: poll, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
}
.setFailureType(to: Error.self)
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Poll> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
extension APIService {
/// vote local
/// # Note
/// Not mark the poll voted so that view model could know when to reveal the results
func vote(
pollObjectID: NSManagedObjectID,
mastodonUserObjectID: NSManagedObjectID,
choices: [Int]
) -> AnyPublisher<Mastodon.Entity.Poll.ID, Error> {
var _targetPollID: Mastodon.Entity.Poll.ID?
var isPollExpired = false
var didVotedLocal = false
let managedObjectContext = backgroundManagedObjectContext
return managedObjectContext.performChanges {
let poll = managedObjectContext.object(with: pollObjectID) as! Poll
let mastodonUser = managedObjectContext.object(with: mastodonUserObjectID) as! MastodonUser
_targetPollID = poll.id
if let expiresAt = poll.expiresAt, Date().timeIntervalSince(expiresAt) > 0 {
isPollExpired = true
poll.update(expired: true)
return
}
let options = poll.options.sorted(by: { $0.index.intValue < $1.index.intValue })
let votedOptions = poll.options.filter { option in
(option.votedBy ?? Set()).map { $0.id }.contains(mastodonUser.id)
}
if !poll.multiple, !votedOptions.isEmpty {
// if did voted for single poll. Do not allow vote again
didVotedLocal = true
return
}
for option in options {
let voted = choices.contains(option.index.intValue)
option.update(voted: voted, by: mastodonUser)
option.didUpdate(at: option.updatedAt) // trigger update without change anything
}
poll.didUpdate(at: poll.updatedAt) // trigger update without change anything
}
.tryMap { result in
guard !isPollExpired else {
throw APIError.explicit(APIError.ErrorReason.voteExpiredPoll)
}
guard !didVotedLocal else {
throw APIError.implicit(APIError.ErrorReason.badRequest)
}
switch result {
case .success:
guard let targetPollID = _targetPollID else {
throw APIError.implicit(.badRequest)
}
return targetPollID
case .failure(let error):
assertionFailure(error.localizedDescription)
throw error
}
}
.eraseToAnyPublisher()
}
/// send vote request to remote
func vote(
domain: String,
pollID: Mastodon.Entity.Poll.ID,
pollObjectID: NSManagedObjectID,
choices: [Int],
mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> {
let authorization = mastodonAuthenticationBox.userAuthorization
let requestMastodonUserID = mastodonAuthenticationBox.userID
let query = Mastodon.API.Polls.VoteQuery(choices: choices)
return Mastodon.API.Polls.vote(
session: session,
domain: domain,
pollID: pollID,
query: query,
authorization: authorization
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Poll>, Error> in
let entity = response.value
let managedObjectContext = self.backgroundManagedObjectContext
return managedObjectContext.performChanges {
let _requestMastodonUser: MastodonUser? = {
let request = MastodonUser.sortedFetchRequest
request.predicate = MastodonUser.predicate(domain: mastodonAuthenticationBox.domain, id: requestMastodonUserID)
request.fetchLimit = 1
request.returnsObjectsAsFaults = false
do {
return try managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}()
guard let requestMastodonUser = _requestMastodonUser else {
assertionFailure()
return
}
guard let poll = managedObjectContext.object(with: pollObjectID) as? Poll else { return }
APIService.CoreData.merge(poll: poll, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
}
.setFailureType(to: Error.self)
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Poll> in
switch result {
case .success:
return response
case .failure(let error):
throw error
}
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}