Put Mastodon API code in separate module from entities

This commit is contained in:
Justin Mazzocchi 2020-09-03 18:55:46 -07:00
parent f6f065e143
commit f921d154b3
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
53 changed files with 170 additions and 101 deletions

View file

@ -38,11 +38,11 @@ struct StoredStatus: Codable, Hashable {
extension StoredStatus: FetchableRecord, PersistableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder()
MastodonDecoder()
}
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
APIEncoder()
MastodonEncoder()
}
}

View file

@ -6,10 +6,10 @@ import Mastodon
extension Account: FetchableRecord, PersistableRecord {
public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder()
MastodonDecoder()
}
public static func databaseJSONEncoder(for column: String) -> JSONEncoder {
APIEncoder()
MastodonEncoder()
}
}

View file

@ -6,10 +6,10 @@ import Mastodon
extension Filter: FetchableRecord, PersistableRecord {
public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder()
MastodonDecoder()
}
public static func databaseJSONEncoder(for column: String) -> JSONEncoder {
APIEncoder()
MastodonEncoder()
}
}

View file

@ -11,24 +11,13 @@ let package = Package(
products: [
.library(
name: "Mastodon",
targets: ["Mastodon"]),
.library(
name: "MastodonStubs",
targets: ["MastodonStubs"])
],
dependencies: [
.package(path: "HTTP")
targets: ["Mastodon"])
],
dependencies: [],
targets: [
.target(
name: "Mastodon",
dependencies: ["HTTP"]),
.target(
name: "MastodonStubs",
dependencies: ["Mastodon", .product(name: "Stubbing", package: "HTTP")],
resources: [.process("Resources")]),
.target(name: "Mastodon"),
.testTarget(
name: "MastodonTests",
dependencies: ["MastodonStubs"])
dependencies: ["Mastodon"])
]
)

View file

@ -2,7 +2,7 @@
import Foundation
public final class APIDecoder: JSONDecoder {
public final class MastodonDecoder: JSONDecoder {
public override init() {
super.init()

View file

@ -2,7 +2,7 @@
import Foundation
public final class APIEncoder: JSONEncoder {
public final class MastodonEncoder: JSONEncoder {
public override init() {
super.init()

View file

@ -12,21 +12,6 @@ public enum Timeline: Hashable {
public extension Timeline {
static let nonLists: [Timeline] = [.home, .local, .federated]
var endpoint: TimelinesEndpoint {
switch self {
case .home:
return .home
case .local:
return .public(local: true)
case .federated:
return .public(local: false)
case let .list(list):
return .list(id: list.id)
case let .tag(tag):
return .tag(tag)
}
}
}
extension Timeline: Identifiable {

5
MastodonAPI/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

35
MastodonAPI/Package.swift Normal file
View file

@ -0,0 +1,35 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "MastodonAPI",
platforms: [
.iOS(.v14),
.macOS(.v11)
],
products: [
.library(
name: "MastodonAPI",
targets: ["MastodonAPI"]),
.library(
name: "MastodonAPIStubs",
targets: ["MastodonAPIStubs"])
],
dependencies: [
.package(path: "HTTP"),
.package(path: "Mastodon")
],
targets: [
.target(
name: "MastodonAPI",
dependencies: ["HTTP", "Mastodon"]),
.target(
name: "MastodonAPIStubs",
dependencies: ["MastodonAPI", .product(name: "Stubbing", package: "HTTP")],
resources: [.process("Resources")]),
.testTarget(
name: "MastodonAPITests",
dependencies: ["MastodonAPIStubs"])
]
)

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum AccessTokenEndpoint {
case oauthToken(

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum AccountEndpoint {
case verifyCredentials

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum AppAuthorizationEndpoint {
case apps(clientName: String, redirectURI: String, scopes: String, website: URL?)

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum ContextEndpoint {
case context(id: String)

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum DeletionEndpoint {
case oauthRevoke(token: String, clientID: String, clientSecret: String)

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum FilterEndpoint {
case create(

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum FiltersEndpoint {
case filters

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum InstanceEndpoint {
case instance

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum ListEndpoint {
case create(title: String)

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum ListsEndpoint {
case lists

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public struct Paged<T: Endpoint> {
public let endpoint: T

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum PreferencesEndpoint {
case preferences

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum PushSubscriptionEndpoint {
case create(

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum StatusEndpoint {
case status(id: String)

View file

@ -2,6 +2,7 @@
import Foundation
import HTTP
import Mastodon
public enum TimelinesEndpoint {
case `public`(local: Bool)

View file

@ -0,0 +1,21 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
public extension Timeline {
var endpoint: TimelinesEndpoint {
switch self {
case .home:
return .home
case .local:
return .public(local: true)
case .federated:
return .public(local: false)
case let .list(list):
return .list(id: list.id)
case let .tag(tag):
return .tag(tag)
}
}
}

View file

@ -3,13 +3,14 @@
import Foundation
import Combine
import HTTP
import Mastodon
public final class APIClient: Client {
public final class MastodonAPIClient: Client {
public var instanceURL: URL?
public var accessToken: String?
public required init(session: Session) {
super.init(session: session, decoder: APIDecoder())
super.init(session: session, decoder: MastodonDecoder())
}
public override func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
@ -17,14 +18,14 @@ public final class APIClient: Client {
}
}
extension APIClient {
extension MastodonAPIClient {
public func request<E: Endpoint>(_ endpoint: E) -> AnyPublisher<E.ResultType, Error> {
guard let instanceURL = instanceURL else {
return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
}
return super.request(
APITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken),
MastodonAPITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken),
decodeErrorsAs: APIError.self)
}
}

View file

@ -3,7 +3,7 @@
import Foundation
import HTTP
public struct APITarget<E: Endpoint> {
public struct MastodonAPITarget<E: Endpoint> {
public let baseURL: URL
public let endpoint: E
public let accessToken: String?
@ -15,7 +15,7 @@ public struct APITarget<E: Endpoint> {
}
}
extension APITarget: DecodableTarget {
extension MastodonAPITarget: DecodableTarget {
public typealias ResultType = E.ResultType
public var pathComponents: [String] { endpoint.pathComponents }

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension AccessTokenEndpoint: Stubbing {

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension AccountEndpoint: Stubbing {

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension AppAuthorizationEndpoint: Stubbing {

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension ContextEndpoint: Stubbing {

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension InstanceEndpoint: Stubbing {

View file

@ -1,10 +1,10 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension APITarget: Stubbing {
extension MastodonAPITarget: Stubbing {
public func stub(url: URL) -> HTTPStub? {
(endpoint as? Stubbing)?.stub(url: url)
}

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension Paged: Stubbing where T: Stubbing {

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension PreferencesEndpoint: Stubbing {

View file

@ -1,7 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import MastodonAPI
import Stubbing
extension TimelinesEndpoint: Stubbing {

View file

@ -0,0 +1,10 @@
import XCTest
@testable import MastodonAPI
final class MastodonAPITests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
}
}

View file

@ -95,6 +95,7 @@
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterView.swift; sourceTree = "<group>"; };
D0BECB952501B3DD002C1B13 /* Keychain */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Keychain; sourceTree = "<group>"; };
D0BECB962501BCE0002C1B13 /* Secrets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Secrets; sourceTree = "<group>"; };
D0BECB9D2501CBC3002C1B13 /* MastodonAPI */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonAPI; sourceTree = "<group>"; };
D0BFDAF524FC7C5300C86618 /* HTTP */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HTTP; sourceTree = "<group>"; };
D0C7D41E24F76169001EBDBB /* Metatext.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Metatext.entitlements; sourceTree = "<group>"; };
D0C7D41F24F76169001EBDBB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -188,6 +189,7 @@
D0BECB952501B3DD002C1B13 /* Keychain */,
D0C7D45624F76169001EBDBB /* Localizations */,
D0E0F1E424FC49FC002C04BF /* Mastodon */,
D0BECB9D2501CBC3002C1B13 /* MastodonAPI */,
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
D047FA8D24C3E21200AF17C5 /* Products */,
D0BECB962501BCE0002C1B13 /* Secrets */,

View file

@ -24,7 +24,7 @@ class NotificationService: UNNotificationServiceExtension {
do {
let decryptedJSON = try Self.extractAndDecrypt(userInfo: request.content.userInfo)
pushNotification = try APIDecoder().decode(PushNotification.self, from: decryptedJSON)
pushNotification = try MastodonDecoder().decode(PushNotification.self, from: decryptedJSON)
} catch {
contentHandler(bestAttemptContent)

View file

@ -20,18 +20,18 @@ let package = Package(
.package(url: "https://github.com/groue/CombineExpectations.git", .upToNextMajor(from: "0.5.0")),
.package(path: "DB"),
.package(path: "Keychain"),
.package(path: "Mastodon"),
.package(path: "MastodonAPI"),
.package(path: "Secrets")
],
targets: [
.target(
name: "ServiceLayer",
dependencies: ["DB", "Secrets"]),
dependencies: ["DB", "MastodonAPI", "Secrets"]),
.target(
name: "ServiceLayerMocks",
dependencies: [
"ServiceLayer",
.product(name: "MastodonStubs", package: "Mastodon"),
.product(name: "MastodonAPIStubs", package: "MastodonAPI"),
.product(name: "MockKeychain", package: "Keychain")]),
.testTarget(
name: "ServiceLayerTests",

View file

@ -4,6 +4,7 @@ import DB
import Foundation
import Combine
import Mastodon
import MastodonAPI
import Secrets
public struct AllIdentitiesService {
@ -53,9 +54,9 @@ public extension AllIdentitiesService {
func deleteIdentity(_ identity: Identity) -> AnyPublisher<Never, Error> {
let secrets = Secrets(identityID: identity.id, keychain: environment.keychain)
let networkClient = APIClient(session: environment.session)
let mastodonAPIClient = MastodonAPIClient(session: environment.session)
networkClient.instanceURL = identity.url
mastodonAPIClient.instanceURL = identity.url
return identityDatabase.deleteIdentity(id: identity.id)
.collect()
@ -65,7 +66,7 @@ public extension AllIdentitiesService {
clientID: try secrets.item(.clientID),
clientSecret: try secrets.item(.clientSecret))
}
.flatMap(networkClient.request)
.flatMap(mastodonAPIClient.request)
.collect()
.tryMap { _ in
try secrets.deleteAllItems()

View file

@ -3,14 +3,15 @@
import Foundation
import Combine
import Mastodon
import MastodonAPI
public struct AuthenticationService {
private let networkClient: APIClient
private let mastodonAPIClient: MastodonAPIClient
private let webAuthSessionType: WebAuthSession.Type
private let webAuthSessionContextProvider = WebAuthSessionContextProvider()
public init(environment: AppEnvironment) {
networkClient = APIClient(session: environment.session)
mastodonAPIClient = MastodonAPIClient(session: environment.session)
webAuthSessionType = environment.webAuthSessionType
}
}
@ -22,9 +23,9 @@ public extension AuthenticationService {
redirectURI: OAuth.callbackURL.absoluteString,
scopes: OAuth.scopes,
website: OAuth.website)
let target = APITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
let target = MastodonAPITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
return networkClient.request(target)
return mastodonAPIClient.request(target)
}
func authenticate(instanceURL: URL, appAuthorization: AppAuthorization) -> AnyPublisher<AccessToken, Error> {
@ -63,9 +64,9 @@ public extension AuthenticationService {
grantType: OAuth.grantType,
scopes: OAuth.scopes,
redirectURI: OAuth.callbackURL.absoluteString)
let target = APITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
let target = MastodonAPITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
return networkClient.request(target)
return mastodonAPIClient.request(target)
}
.eraseToAnyPublisher()
}

View file

@ -4,6 +4,7 @@ import DB
import Foundation
import Combine
import Mastodon
import MastodonAPI
import Secrets
public class IdentityService {
@ -13,7 +14,7 @@ public class IdentityService {
private let identityDatabase: IdentityDatabase
private let contentDatabase: ContentDatabase
private let environment: AppEnvironment
private let networkClient: APIClient
private let mastodonAPIClient: MastodonAPIClient
private let secrets: Secrets
private let observationErrorsInput = PassthroughSubject<Error, Never>()
@ -37,9 +38,9 @@ public class IdentityService {
secrets = Secrets(
identityID: identityID,
keychain: environment.keychain)
networkClient = APIClient(session: environment.session)
networkClient.instanceURL = identity.url
networkClient.accessToken = try? secrets.item(.accessToken)
mastodonAPIClient = MastodonAPIClient(session: environment.session)
mastodonAPIClient.instanceURL = identity.url
mastodonAPIClient.accessToken = try? secrets.item(.accessToken)
contentDatabase = try ContentDatabase(identityID: identityID, inMemory: environment.inMemoryContent)
@ -53,21 +54,21 @@ public class IdentityService {
}
public extension IdentityService {
var isAuthorized: Bool { networkClient.accessToken != nil }
var isAuthorized: Bool { mastodonAPIClient.accessToken != nil }
func updateLastUse() -> AnyPublisher<Never, Error> {
identityDatabase.updateLastUsedAt(identityID: identity.id)
}
func verifyCredentials() -> AnyPublisher<Never, Error> {
networkClient.request(AccountEndpoint.verifyCredentials)
mastodonAPIClient.request(AccountEndpoint.verifyCredentials)
.zip(Just(identity.id).first().setFailureType(to: Error.self))
.flatMap(identityDatabase.updateAccount)
.eraseToAnyPublisher()
}
func refreshServerPreferences() -> AnyPublisher<Never, Error> {
networkClient.request(PreferencesEndpoint.preferences)
mastodonAPIClient.request(PreferencesEndpoint.preferences)
.zip(Just(self).first().setFailureType(to: Error.self))
.map { ($1.identity.preferences.updated(from: $0), $1.identity.id) }
.flatMap(identityDatabase.updatePreferences)
@ -75,7 +76,7 @@ public extension IdentityService {
}
func refreshInstance() -> AnyPublisher<Never, Error> {
networkClient.request(InstanceEndpoint.instance)
mastodonAPIClient.request(InstanceEndpoint.instance)
.zip(Just(identity.id).first().setFailureType(to: Error.self))
.flatMap(identityDatabase.updateInstance)
.eraseToAnyPublisher()
@ -90,19 +91,19 @@ public extension IdentityService {
}
func refreshLists() -> AnyPublisher<Never, Error> {
networkClient.request(ListsEndpoint.lists)
mastodonAPIClient.request(ListsEndpoint.lists)
.flatMap(contentDatabase.setLists(_:))
.eraseToAnyPublisher()
}
func createList(title: String) -> AnyPublisher<Never, Error> {
networkClient.request(ListEndpoint.create(title: title))
mastodonAPIClient.request(ListEndpoint.create(title: title))
.flatMap(contentDatabase.createList(_:))
.eraseToAnyPublisher()
}
func deleteList(id: String) -> AnyPublisher<Never, Error> {
networkClient.request(DeletionEndpoint.list(id: id))
mastodonAPIClient.request(DeletionEndpoint.list(id: id))
.map { _ in id }
.flatMap(contentDatabase.deleteList(id:))
.eraseToAnyPublisher()
@ -113,13 +114,13 @@ public extension IdentityService {
}
func refreshFilters() -> AnyPublisher<Never, Error> {
networkClient.request(FiltersEndpoint.filters)
mastodonAPIClient.request(FiltersEndpoint.filters)
.flatMap(contentDatabase.setFilters(_:))
.eraseToAnyPublisher()
}
func createFilter(_ filter: Filter) -> AnyPublisher<Never, Error> {
networkClient.request(FilterEndpoint.create(phrase: filter.phrase,
mastodonAPIClient.request(FilterEndpoint.create(phrase: filter.phrase,
context: filter.context,
irreversible: filter.irreversible,
wholeWord: filter.wholeWord,
@ -129,7 +130,7 @@ public extension IdentityService {
}
func updateFilter(_ filter: Filter) -> AnyPublisher<Never, Error> {
networkClient.request(FilterEndpoint.update(id: filter.id,
mastodonAPIClient.request(FilterEndpoint.update(id: filter.id,
phrase: filter.phrase,
context: filter.context,
irreversible: filter.irreversible,
@ -140,7 +141,7 @@ public extension IdentityService {
}
func deleteFilter(id: String) -> AnyPublisher<Never, Error> {
networkClient.request(DeletionEndpoint.filter(id: id))
mastodonAPIClient.request(DeletionEndpoint.filter(id: id))
.map { _ in id }
.flatMap(contentDatabase.deleteFilter(id:))
.eraseToAnyPublisher()
@ -180,7 +181,7 @@ public extension IdentityService {
.appendingPathComponent(deviceToken)
.appendingPathComponent(identityID.uuidString)
return networkClient.request(
return mastodonAPIClient.request(
PushSubscriptionEndpoint.create(
endpoint: endpoint,
publicKey: publicKey,
@ -194,14 +195,14 @@ public extension IdentityService {
func updatePushSubscription(alerts: PushSubscription.Alerts) -> AnyPublisher<Never, Error> {
let identityID = identity.id
return networkClient.request(PushSubscriptionEndpoint.update(alerts: alerts))
return mastodonAPIClient.request(PushSubscriptionEndpoint.update(alerts: alerts))
.map { ($0.alerts, nil, identityID) }
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:))
.eraseToAnyPublisher()
}
func service(timeline: Timeline) -> StatusListService {
StatusListService(timeline: timeline, networkClient: networkClient, contentDatabase: contentDatabase)
StatusListService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
}
}

View file

@ -4,6 +4,7 @@ import Combine
import DB
import Foundation
import Mastodon
import MastodonAPI
public struct StatusListService {
public let statusSections: AnyPublisher<[[Status]], Error>
@ -11,13 +12,13 @@ public struct StatusListService {
public let contextParentID: String?
private let filterContext: Filter.Context
private let networkClient: APIClient
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
private let requestClosure: (_ maxID: String?, _ minID: String?) -> AnyPublisher<Never, Error>
}
extension StatusListService {
init(timeline: Timeline, networkClient: APIClient, contentDatabase: ContentDatabase) {
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
let filterContext: Filter.Context
switch timeline {
@ -31,9 +32,9 @@ extension StatusListService {
paginates: true,
contextParentID: nil,
filterContext: filterContext,
networkClient: networkClient,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) { maxID, minID in
networkClient.request(Paged(timeline.endpoint, maxID: maxID, minID: minID))
mastodonAPIClient.request(Paged(timeline.endpoint, maxID: maxID, minID: minID))
.flatMap { contentDatabase.insert(statuses: $0, timeline: timeline) }
.eraseToAnyPublisher()
}
@ -50,7 +51,7 @@ public extension StatusListService {
}
func statusService(status: Status) -> StatusService {
StatusService(status: status, networkClient: networkClient, contentDatabase: contentDatabase)
StatusService(status: status, networkClient: mastodonAPIClient, contentDatabase: contentDatabase)
}
func contextService(statusID: String) -> Self {
@ -58,13 +59,13 @@ public extension StatusListService {
paginates: false,
contextParentID: statusID,
filterContext: .thread,
networkClient: networkClient,
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) { _, _ in
Publishers.Merge(
networkClient.request(StatusEndpoint.status(id: statusID))
mastodonAPIClient.request(StatusEndpoint.status(id: statusID))
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher(),
networkClient.request(ContextEndpoint.context(id: statusID))
mastodonAPIClient.request(ContextEndpoint.context(id: statusID))
.flatMap { contentDatabase.insert(context: $0, parentID: statusID) }
.eraseToAnyPublisher())
.eraseToAnyPublisher()

View file

@ -4,24 +4,25 @@ import Foundation
import Combine
import DB
import Mastodon
import MastodonAPI
public struct StatusService {
public let status: Status
private let networkClient: APIClient
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
init(status: Status, networkClient: APIClient, contentDatabase: ContentDatabase) {
init(status: Status, networkClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.status = status
self.networkClient = networkClient
self.mastodonAPIClient = networkClient
self.contentDatabase = contentDatabase
}
}
public extension StatusService {
func toggleFavorited() -> AnyPublisher<Never, Error> {
networkClient.request(status.displayStatus.favourited
? StatusEndpoint.unfavourite(id: status.displayStatus.id)
: StatusEndpoint.favourite(id: status.displayStatus.id))
mastodonAPIClient.request(status.displayStatus.favourited
? StatusEndpoint.unfavourite(id: status.displayStatus.id)
: StatusEndpoint.favourite(id: status.displayStatus.id))
.flatMap(contentDatabase.insert(status:))
.eraseToAnyPublisher()
}

View file

@ -4,12 +4,13 @@ import Foundation
import Combine
import HTTP
import Mastodon
import MastodonStubs
import MastodonAPI
import MastodonAPIStubs
import ServiceLayer
import ServiceLayerMocks
import ViewModels
private let decoder = APIDecoder()
private let decoder = MastodonDecoder()
private let devInstanceURL = URL(string: "https://mastodon.social")!
// swiftlint:disable force_try