mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-24 17:20:59 +00:00
The great id cleanup
This commit is contained in:
parent
9ac6ed2d93
commit
15d6e10edc
63 changed files with 345 additions and 309 deletions
|
@ -1,5 +1,6 @@
|
|||
disabled_rules:
|
||||
- identifier_name
|
||||
- type_name
|
||||
# Swift 5.3
|
||||
- multiple_closures_with_trailing_closure
|
||||
- no_space_in_method_call
|
||||
|
|
|
@ -4,13 +4,17 @@ import Foundation
|
|||
import GRDB
|
||||
|
||||
public struct AccountList: Codable, FetchableRecord, PersistableRecord {
|
||||
let id: UUID
|
||||
let id: Id
|
||||
|
||||
public init() {
|
||||
id = UUID()
|
||||
id = Id()
|
||||
}
|
||||
}
|
||||
|
||||
public extension AccountList {
|
||||
typealias Id = UUID
|
||||
}
|
||||
|
||||
extension AccountList {
|
||||
static let joins = hasMany(AccountListJoin.self).order(AccountListJoin.Columns.index)
|
||||
static let accounts = hasMany(
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct AccountListJoin: Codable, FetchableRecord, PersistableRecord {
|
||||
let accountId: String
|
||||
let listId: UUID
|
||||
let accountId: Account.Id
|
||||
let listId: AccountList.Id
|
||||
let index: Int
|
||||
|
||||
static let account = belongsTo(AccountRecord.self)
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct AccountPinnedStatusJoin: Codable, FetchableRecord, PersistableRecord {
|
||||
let accountId: String
|
||||
let statusId: String
|
||||
let accountId: Account.Id
|
||||
let statusId: Status.Id
|
||||
let index: Int
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import GRDB
|
|||
import Mastodon
|
||||
|
||||
struct AccountRecord: Codable, Hashable {
|
||||
let id: String
|
||||
let id: Account.Id
|
||||
let username: String
|
||||
let acct: String
|
||||
let displayName: String
|
||||
|
@ -24,7 +24,7 @@ struct AccountRecord: Codable, Hashable {
|
|||
let emojis: [Emoji]
|
||||
let bot: Bool
|
||||
let discoverable: Bool
|
||||
let movedId: String?
|
||||
let movedId: Account.Id?
|
||||
}
|
||||
|
||||
extension AccountRecord {
|
||||
|
|
|
@ -12,15 +12,15 @@ public struct ContentDatabase {
|
|||
|
||||
private let databaseWriter: DatabaseWriter
|
||||
|
||||
public init(identityID: UUID, inMemory: Bool, keychain: Keychain.Type) throws {
|
||||
public init(id: Identity.Id, inMemory: Bool, keychain: Keychain.Type) throws {
|
||||
if inMemory {
|
||||
databaseWriter = DatabaseQueue()
|
||||
} else {
|
||||
let path = try Self.fileURL(identityID: identityID).path
|
||||
let path = try Self.fileURL(id: id).path
|
||||
var configuration = Configuration()
|
||||
|
||||
configuration.prepareDatabase {
|
||||
try $0.usePassphrase(Secrets.databaseKey(identityID: identityID, keychain: keychain))
|
||||
try $0.usePassphrase(Secrets.databaseKey(identityId: id, keychain: keychain))
|
||||
}
|
||||
|
||||
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
||||
|
@ -39,8 +39,8 @@ public struct ContentDatabase {
|
|||
}
|
||||
|
||||
public extension ContentDatabase {
|
||||
static func delete(forIdentityID identityID: UUID) throws {
|
||||
try FileManager.default.removeItem(at: fileURL(identityID: identityID))
|
||||
static func delete(id: Identity.Id) throws {
|
||||
try FileManager.default.removeItem(at: fileURL(id: id))
|
||||
}
|
||||
|
||||
func insert(status: Status) -> AnyPublisher<Never, Error> {
|
||||
|
@ -58,7 +58,7 @@ public extension ContentDatabase {
|
|||
|
||||
try timelineRecord.save($0)
|
||||
|
||||
let maxIDPresent = try String.fetchOne($0, timelineRecord.statuses.select(max(StatusRecord.Columns.id)))
|
||||
let maxIdPresent = try String.fetchOne($0, timelineRecord.statuses.select(max(StatusRecord.Columns.id)))
|
||||
|
||||
for status in statuses {
|
||||
try status.save($0)
|
||||
|
@ -66,13 +66,13 @@ public extension ContentDatabase {
|
|||
try TimelineStatusJoin(timelineId: timeline.id, statusId: status.id).save($0)
|
||||
}
|
||||
|
||||
if let maxIDPresent = maxIDPresent,
|
||||
let minIDInserted = statuses.map(\.id).min(),
|
||||
minIDInserted > maxIDPresent {
|
||||
if let maxIdPresent = maxIdPresent,
|
||||
let minIdInserted = statuses.map(\.id).min(),
|
||||
minIdInserted > maxIdPresent {
|
||||
try LoadMoreRecord(
|
||||
timelineId: timeline.id,
|
||||
afterStatusId: minIDInserted,
|
||||
beforeStatusId: maxIDPresent)
|
||||
afterStatusId: minIdInserted,
|
||||
beforeStatusId: maxIdPresent)
|
||||
.save($0)
|
||||
}
|
||||
|
||||
|
@ -86,18 +86,18 @@ public extension ContentDatabase {
|
|||
|
||||
switch direction {
|
||||
case .up:
|
||||
if let maxIDInserted = statuses.map(\.id).max(), maxIDInserted < loadMore.afterStatusId {
|
||||
if let maxIdInserted = statuses.map(\.id).max(), maxIdInserted < loadMore.afterStatusId {
|
||||
try LoadMoreRecord(
|
||||
timelineId: loadMore.timeline.id,
|
||||
afterStatusId: loadMore.afterStatusId,
|
||||
beforeStatusId: maxIDInserted)
|
||||
beforeStatusId: maxIdInserted)
|
||||
.save($0)
|
||||
}
|
||||
case .down:
|
||||
if let minIDInserted = statuses.map(\.id).min(), minIDInserted > loadMore.beforeStatusId {
|
||||
if let minIdInserted = statuses.map(\.id).min(), minIdInserted > loadMore.beforeStatusId {
|
||||
try LoadMoreRecord(
|
||||
timelineId: loadMore.timeline.id,
|
||||
afterStatusId: minIDInserted,
|
||||
afterStatusId: minIdInserted,
|
||||
beforeStatusId: loadMore.beforeStatusId)
|
||||
.save($0)
|
||||
}
|
||||
|
@ -107,25 +107,25 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func insert(context: Context, parentID: String) -> AnyPublisher<Never, Error> {
|
||||
func insert(context: Context, parentId: Status.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
for (index, status) in context.ancestors.enumerated() {
|
||||
try status.save($0)
|
||||
try StatusAncestorJoin(parentId: parentID, statusId: status.id, index: index).save($0)
|
||||
try StatusAncestorJoin(parentId: parentId, statusId: status.id, index: index).save($0)
|
||||
}
|
||||
|
||||
for (index, status) in context.descendants.enumerated() {
|
||||
try status.save($0)
|
||||
try StatusDescendantJoin(parentId: parentID, statusId: status.id, index: index).save($0)
|
||||
try StatusDescendantJoin(parentId: parentId, statusId: status.id, index: index).save($0)
|
||||
}
|
||||
|
||||
try StatusAncestorJoin.filter(
|
||||
StatusAncestorJoin.Columns.parentId == parentID
|
||||
StatusAncestorJoin.Columns.parentId == parentId
|
||||
&& !context.ancestors.map(\.id).contains(StatusAncestorJoin.Columns.statusId))
|
||||
.deleteAll($0)
|
||||
|
||||
try StatusDescendantJoin.filter(
|
||||
StatusDescendantJoin.Columns.parentId == parentID
|
||||
StatusDescendantJoin.Columns.parentId == parentId
|
||||
&& !context.descendants.map(\.id).contains(StatusDescendantJoin.Columns.statusId))
|
||||
.deleteAll($0)
|
||||
}
|
||||
|
@ -133,15 +133,15 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func insert(pinnedStatuses: [Status], accountID: String) -> AnyPublisher<Never, Error> {
|
||||
func insert(pinnedStatuses: [Status], accountId: Account.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
for (index, status) in pinnedStatuses.enumerated() {
|
||||
try status.save($0)
|
||||
try AccountPinnedStatusJoin(accountId: accountID, statusId: status.id, index: index).save($0)
|
||||
try AccountPinnedStatusJoin(accountId: accountId, statusId: status.id, index: index).save($0)
|
||||
}
|
||||
|
||||
try AccountPinnedStatusJoin.filter(
|
||||
AccountPinnedStatusJoin.Columns.accountId == accountID
|
||||
AccountPinnedStatusJoin.Columns.accountId == accountId
|
||||
&& !pinnedStatuses.map(\.id).contains(AccountPinnedStatusJoin.Columns.statusId))
|
||||
.deleteAll($0)
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteList(id: String) -> AnyPublisher<Never, Error> {
|
||||
func deleteList(id: List.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: TimelineRecord.filter(TimelineRecord.Columns.listId == id).deleteAll)
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -209,7 +209,7 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteFilter(id: String) -> AnyPublisher<Never, Error> {
|
||||
func deleteFilter(id: Filter.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: Filter.filter(Filter.Columns.id == id).deleteAll)
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -225,9 +225,9 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func contextObservation(parentID: String) -> AnyPublisher<[[CollectionItem]], Error> {
|
||||
func contextObservation(id: Status.Id) -> AnyPublisher<[[CollectionItem]], Error> {
|
||||
ValueObservation.tracking(
|
||||
ContextItemsInfo.request(StatusRecord.filter(StatusRecord.Columns.id == parentID)).fetchOne)
|
||||
ContextItemsInfo.request(StatusRecord.filter(StatusRecord.Columns.id == id)).fetchOne)
|
||||
.removeDuplicates()
|
||||
.publisher(in: databaseWriter)
|
||||
.combineLatest(activeFiltersPublisher)
|
||||
|
@ -252,7 +252,7 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func accountObservation(id: String) -> AnyPublisher<Account, Error> {
|
||||
func accountObservation(id: Account.Id) -> AnyPublisher<Account, Error> {
|
||||
ValueObservation.tracking(AccountInfo.request(AccountRecord.filter(AccountRecord.Columns.id == id)).fetchOne)
|
||||
.removeDuplicates()
|
||||
.publisher(in: databaseWriter)
|
||||
|
@ -271,8 +271,8 @@ public extension ContentDatabase {
|
|||
}
|
||||
|
||||
private extension ContentDatabase {
|
||||
static func fileURL(identityID: UUID) throws -> URL {
|
||||
try FileManager.default.databaseDirectoryURL(name: identityID.uuidString)
|
||||
static func fileURL(id: Identity.Id) throws -> URL {
|
||||
try FileManager.default.databaseDirectoryURL(name: id.uuidString)
|
||||
}
|
||||
|
||||
static func clean(_ databaseWriter: DatabaseWriter) throws {
|
||||
|
|
|
@ -5,9 +5,9 @@ import GRDB
|
|||
import Mastodon
|
||||
|
||||
struct LoadMoreRecord: Codable, Hashable {
|
||||
let timelineId: String
|
||||
let afterStatusId: String
|
||||
let beforeStatusId: String
|
||||
let timelineId: Timeline.Id
|
||||
let afterStatusId: Status.Id
|
||||
let beforeStatusId: Status.Id
|
||||
}
|
||||
|
||||
extension LoadMoreRecord {
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct StatusAncestorJoin: Codable, FetchableRecord, PersistableRecord {
|
||||
let parentId: String
|
||||
let statusId: String
|
||||
let parentId: Status.Id
|
||||
let statusId: Status.Id
|
||||
let index: Int
|
||||
|
||||
static let status = belongsTo(StatusRecord.self, using: ForeignKey([Columns.statusId]))
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct StatusDescendantJoin: Codable, FetchableRecord, PersistableRecord {
|
||||
let parentId: String
|
||||
let statusId: String
|
||||
let parentId: Status.Id
|
||||
let statusId: Status.Id
|
||||
let index: Int
|
||||
|
||||
static let status = belongsTo(StatusRecord.self, using: ForeignKey([Columns.statusId]))
|
||||
|
|
|
@ -5,10 +5,10 @@ import GRDB
|
|||
import Mastodon
|
||||
|
||||
struct StatusRecord: Codable, Hashable {
|
||||
let id: String
|
||||
let id: Status.Id
|
||||
let uri: String
|
||||
let createdAt: Date
|
||||
let accountId: String
|
||||
let accountId: Account.Id
|
||||
let content: HTML
|
||||
let visibility: Status.Visibility
|
||||
let sensitive: Bool
|
||||
|
@ -22,9 +22,9 @@ struct StatusRecord: Codable, Hashable {
|
|||
let repliesCount: Int
|
||||
let application: Application?
|
||||
let url: URL?
|
||||
let inReplyToId: String?
|
||||
let inReplyToAccountId: String?
|
||||
let reblogId: String?
|
||||
let inReplyToId: Status.Id?
|
||||
let inReplyToAccountId: Account.Id?
|
||||
let reblogId: Status.Id?
|
||||
let poll: Poll?
|
||||
let card: Card?
|
||||
let language: String?
|
||||
|
|
|
@ -5,11 +5,11 @@ import GRDB
|
|||
import Mastodon
|
||||
|
||||
struct TimelineRecord: Codable, Hashable {
|
||||
let id: String
|
||||
let listId: String?
|
||||
let id: Timeline.Id
|
||||
let listId: List.Id?
|
||||
let listTitle: String?
|
||||
let tag: String?
|
||||
let accountId: String?
|
||||
let accountId: Account.Id?
|
||||
let profileCollection: ProfileCollection?
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct TimelineStatusJoin: Codable, FetchableRecord, PersistableRecord {
|
||||
let timelineId: String
|
||||
let statusId: String
|
||||
let timelineId: Timeline.Id
|
||||
let statusId: Status.Id
|
||||
|
||||
static let status = belongsTo(StatusRecord.self)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import Foundation
|
|||
import Mastodon
|
||||
|
||||
public struct Identity: Codable, Hashable, Identifiable {
|
||||
public let id: UUID
|
||||
public let id: Id
|
||||
public let url: URL
|
||||
public let authenticated: Bool
|
||||
public let pending: Bool
|
||||
|
@ -17,6 +17,8 @@ public struct Identity: Codable, Hashable, Identifiable {
|
|||
}
|
||||
|
||||
public extension Identity {
|
||||
typealias Id = UUID
|
||||
|
||||
struct Instance: Codable, Hashable {
|
||||
public let uri: String
|
||||
public let streamingAPI: URL
|
||||
|
@ -25,8 +27,8 @@ public extension Identity {
|
|||
}
|
||||
|
||||
struct Account: Codable, Hashable {
|
||||
public let id: String
|
||||
public let identityID: UUID
|
||||
public let id: Mastodon.Account.Id
|
||||
public let identityId: Identity.Id
|
||||
public let username: String
|
||||
public let displayName: String
|
||||
public let url: URL
|
||||
|
|
|
@ -4,12 +4,12 @@ import Foundation
|
|||
import Mastodon
|
||||
|
||||
public struct IdentityFixture {
|
||||
public let id: UUID
|
||||
public let id: Identity.Id
|
||||
public let instanceURL: URL
|
||||
public let instance: Instance?
|
||||
public let account: Account?
|
||||
|
||||
public init(id: UUID, instanceURL: URL, instance: Instance?, account: Account?) {
|
||||
public init(id: Identity.Id, instanceURL: URL, instance: Instance?, account: Account?) {
|
||||
self.id = id
|
||||
self.instanceURL = instanceURL
|
||||
self.instance = instance
|
||||
|
|
|
@ -5,8 +5,8 @@ import Mastodon
|
|||
|
||||
public struct LoadMore: Hashable {
|
||||
public let timeline: Timeline
|
||||
public let afterStatusId: String
|
||||
public let beforeStatusId: String
|
||||
public let afterStatusId: Status.Id
|
||||
public let beforeStatusId: Status.Id
|
||||
}
|
||||
|
||||
public extension LoadMore {
|
||||
|
|
|
@ -9,10 +9,12 @@ public enum Timeline: Hashable {
|
|||
case federated
|
||||
case list(List)
|
||||
case tag(String)
|
||||
case profile(accountId: String, profileCollection: ProfileCollection)
|
||||
case profile(accountId: Account.Id, profileCollection: ProfileCollection)
|
||||
}
|
||||
|
||||
public extension Timeline {
|
||||
typealias Id = String
|
||||
|
||||
static let unauthenticatedDefaults: [Timeline] = [.local, .federated]
|
||||
static let authenticatedDefaults: [Timeline] = [.home, .local, .federated]
|
||||
|
||||
|
@ -29,7 +31,7 @@ public extension Timeline {
|
|||
}
|
||||
|
||||
extension Timeline: Identifiable {
|
||||
public var id: String {
|
||||
public var id: Id {
|
||||
switch self {
|
||||
case .home:
|
||||
return "home"
|
||||
|
|
|
@ -28,7 +28,7 @@ extension IdentityDatabase {
|
|||
|
||||
try db.create(table: "account", ifNotExists: true) { t in
|
||||
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||
t.column("identityID", .text).notNull()
|
||||
t.column("identityId", .text).notNull()
|
||||
.references("identityRecord", onDelete: .cascade)
|
||||
t.column("username", .text).notNull()
|
||||
t.column("displayName", .text).notNull()
|
||||
|
|
|
@ -22,7 +22,7 @@ public struct IdentityDatabase {
|
|||
var configuration = Configuration()
|
||||
|
||||
configuration.prepareDatabase {
|
||||
try $0.usePassphrase(Secrets.databaseKey(identityID: nil, keychain: keychain))
|
||||
try $0.usePassphrase(Secrets.databaseKey(identityId: nil, keychain: keychain))
|
||||
}
|
||||
|
||||
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
||||
|
@ -33,7 +33,7 @@ public struct IdentityDatabase {
|
|||
}
|
||||
|
||||
public extension IdentityDatabase {
|
||||
func createIdentity(id: UUID, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> {
|
||||
func createIdentity(id: Identity.Id, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(
|
||||
updates: IdentityRecord(
|
||||
id: id,
|
||||
|
@ -50,23 +50,23 @@ public extension IdentityDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> {
|
||||
func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: IdentityRecord.filter(IdentityRecord.Columns.id == id).deleteAll)
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updateLastUsedAt(identityID: UUID) -> AnyPublisher<Never, Error> {
|
||||
func updateLastUsedAt(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
try IdentityRecord
|
||||
.filter(IdentityRecord.Columns.id == identityID)
|
||||
.filter(IdentityRecord.Columns.id == id)
|
||||
.updateAll($0, IdentityRecord.Columns.lastUsedAt.set(to: Date()))
|
||||
}
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updateInstance(_ instance: Instance, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
|
||||
func updateInstance(_ instance: Instance, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
try Identity.Instance(
|
||||
uri: instance.uri,
|
||||
|
@ -75,18 +75,18 @@ public extension IdentityDatabase {
|
|||
thumbnail: instance.thumbnail)
|
||||
.save($0)
|
||||
try IdentityRecord
|
||||
.filter(IdentityRecord.Columns.id == identityID)
|
||||
.filter(IdentityRecord.Columns.id == id)
|
||||
.updateAll($0, IdentityRecord.Columns.instanceURI.set(to: instance.uri))
|
||||
}
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updateAccount(_ account: Account, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
|
||||
func updateAccount(_ account: Account, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(
|
||||
updates: Identity.Account(
|
||||
id: account.id,
|
||||
identityID: identityID,
|
||||
identityId: id,
|
||||
username: account.username,
|
||||
displayName: account.displayName,
|
||||
url: account.url,
|
||||
|
@ -100,7 +100,7 @@ public extension IdentityDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func confirmIdentity(id: UUID) -> AnyPublisher<Never, Error> {
|
||||
func confirmIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
try IdentityRecord
|
||||
.filter(IdentityRecord.Columns.id == id)
|
||||
|
@ -110,43 +110,41 @@ public extension IdentityDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updatePreferences(_ preferences: Mastodon.Preferences,
|
||||
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
|
||||
func updatePreferences(_ preferences: Mastodon.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == identityID)
|
||||
guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == id)
|
||||
.fetchOne($0)?
|
||||
.preferences else {
|
||||
throw IdentityDatabaseError.identityNotFound
|
||||
}
|
||||
|
||||
try Self.writePreferences(storedPreferences.updated(from: preferences), id: identityID)($0)
|
||||
try Self.writePreferences(storedPreferences.updated(from: preferences), id: id)($0)
|
||||
}
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updatePreferences(_ preferences: Identity.Preferences,
|
||||
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: identityID))
|
||||
func updatePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: id))
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updatePushSubscription(alerts: PushSubscription.Alerts,
|
||||
deviceToken: Data? = nil,
|
||||
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
|
||||
id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
let data = try IdentityRecord.databaseJSONEncoder(
|
||||
for: IdentityRecord.Columns.pushSubscriptionAlerts.name)
|
||||
.encode(alerts)
|
||||
|
||||
try IdentityRecord
|
||||
.filter(IdentityRecord.Columns.id == identityID)
|
||||
.filter(IdentityRecord.Columns.id == id)
|
||||
.updateAll($0, IdentityRecord.Columns.pushSubscriptionAlerts.set(to: data))
|
||||
|
||||
if let deviceToken = deviceToken {
|
||||
try IdentityRecord
|
||||
.filter(IdentityRecord.Columns.id == identityID)
|
||||
.filter(IdentityRecord.Columns.id == id)
|
||||
.updateAll($0, IdentityRecord.Columns.lastRegisteredDeviceToken.set(to: deviceToken))
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +152,7 @@ public extension IdentityDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func identityObservation(id: UUID, immediate: Bool) -> AnyPublisher<Identity, Error> {
|
||||
func identityObservation(id: Identity.Id, immediate: Bool) -> AnyPublisher<Identity, Error> {
|
||||
ValueObservation.tracking(
|
||||
IdentityInfo.request(IdentityRecord.filter(IdentityRecord.Columns.id == id)).fetchOne)
|
||||
.removeDuplicates()
|
||||
|
@ -176,7 +174,7 @@ public extension IdentityDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func recentIdentitiesObservation(excluding: UUID) -> AnyPublisher<[Identity], Error> {
|
||||
func recentIdentitiesObservation(excluding: Identity.Id) -> AnyPublisher<[Identity], Error> {
|
||||
ValueObservation.tracking(
|
||||
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
|
||||
.filter(IdentityRecord.Columns.id != excluding)
|
||||
|
@ -188,7 +186,7 @@ public extension IdentityDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> {
|
||||
func immediateMostRecentlyUsedIdentityIdObservation() -> AnyPublisher<Identity.Id?, Error> {
|
||||
ValueObservation.tracking(
|
||||
IdentityRecord.select(IdentityRecord.Columns.id)
|
||||
.order(IdentityRecord.Columns.lastUsedAt.desc).fetchOne)
|
||||
|
@ -210,7 +208,7 @@ public extension IdentityDatabase {
|
|||
private extension IdentityDatabase {
|
||||
static let name = "identity"
|
||||
|
||||
static func writePreferences(_ preferences: Identity.Preferences, id: UUID) -> (Database) throws -> Void {
|
||||
static func writePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> (Database) throws -> Void {
|
||||
{
|
||||
let data = try IdentityRecord.databaseJSONEncoder(
|
||||
for: IdentityRecord.Columns.preferences.name).encode(preferences)
|
||||
|
|
|
@ -5,7 +5,7 @@ import GRDB
|
|||
import Mastodon
|
||||
|
||||
struct IdentityRecord: Codable, Hashable, FetchableRecord, PersistableRecord {
|
||||
let id: UUID
|
||||
let id: Identity.Id
|
||||
let url: URL
|
||||
let authenticated: Bool
|
||||
let pending: Bool
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public typealias HTTPStub = Result<(URLResponse, Data), Error>
|
||||
public typealias HTTPStub = Result<(HTTPURLResponse, Data), Error>
|
||||
|
||||
public protocol Stubbing {
|
||||
func stub(url: URL) -> HTTPStub?
|
||||
|
|
|
@ -9,7 +9,7 @@ public final class Account: Codable, Identifiable {
|
|||
public let verifiedAt: Date?
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let id: Id
|
||||
public let username: String
|
||||
public let acct: String
|
||||
public let displayName: String
|
||||
|
@ -30,7 +30,7 @@ public final class Account: Codable, Identifiable {
|
|||
@DecodableDefault.False public private(set) var discoverable: Bool
|
||||
public var moved: Account?
|
||||
|
||||
public init(id: String,
|
||||
public init(id: Id,
|
||||
username: String,
|
||||
acct: String,
|
||||
displayName: String,
|
||||
|
@ -73,6 +73,10 @@ public final class Account: Codable, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
public extension Account {
|
||||
typealias Id = String
|
||||
}
|
||||
|
||||
extension Account: Hashable {
|
||||
public static func == (lhs: Account, rhs: Account) -> Bool {
|
||||
return lhs.id == rhs.id &&
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
|
||||
public struct AppAuthorization: Codable {
|
||||
public let id: String
|
||||
public let id: Id
|
||||
public let clientId: String
|
||||
public let clientSecret: String
|
||||
public let name: String
|
||||
|
@ -11,3 +11,7 @@ public struct AppAuthorization: Codable {
|
|||
public let website: String?
|
||||
public let vapidKey: String?
|
||||
}
|
||||
|
||||
public extension AppAuthorization {
|
||||
typealias Id = String
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public struct Attachment: Codable, Hashable {
|
|||
}
|
||||
// swiftlint:enable nesting
|
||||
|
||||
public let id: String
|
||||
public let id: Id
|
||||
public let type: AttachmentType
|
||||
public let url: URL
|
||||
public let remoteUrl: URL?
|
||||
|
@ -41,3 +41,7 @@ public struct Attachment: Codable, Hashable {
|
|||
public let meta: Meta?
|
||||
public let description: String?
|
||||
}
|
||||
|
||||
public extension Attachment {
|
||||
typealias Id = String
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public struct Filter: Codable, Hashable, Identifiable {
|
|||
public static var unknownCase: Self { .unknown }
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let id: Id
|
||||
public var phrase: String
|
||||
public var context: [Context]
|
||||
public var expiresAt: Date?
|
||||
|
@ -23,8 +23,10 @@ public struct Filter: Codable, Hashable, Identifiable {
|
|||
}
|
||||
|
||||
public extension Filter {
|
||||
static let newFilterID: String = "com.metabolist.metatext.new-filter-id"
|
||||
static let new = Self(id: newFilterID,
|
||||
typealias Id = String
|
||||
|
||||
static let newFilterId: Id = "com.metabolist.metatext.new-filter-id"
|
||||
static let new = Self(id: newFilterId,
|
||||
phrase: "",
|
||||
context: [],
|
||||
expiresAt: nil,
|
||||
|
|
|
@ -3,11 +3,15 @@
|
|||
import Foundation
|
||||
|
||||
public struct List: Codable, Hashable, Identifiable {
|
||||
public let id: String
|
||||
public let id: Id
|
||||
public let title: String
|
||||
|
||||
public init(id: String, title: String) {
|
||||
public init(id: Id, title: String) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
|
||||
public extension List {
|
||||
typealias Id = String
|
||||
}
|
||||
|
|
|
@ -6,5 +6,5 @@ public struct Mention: Codable, Hashable {
|
|||
public let url: URL
|
||||
public let username: String
|
||||
public let acct: String
|
||||
public let id: String
|
||||
public let id: Account.Id
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ public struct Poll: Codable, Hashable {
|
|||
public var votesCount: Int
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let id: Id
|
||||
public let expiresAt: Date
|
||||
public let expired: Bool
|
||||
public let multiple: Bool
|
||||
|
@ -19,3 +19,7 @@ public struct Poll: Codable, Hashable {
|
|||
public let options: [Option]
|
||||
public let emojis: [Emoji]
|
||||
}
|
||||
|
||||
public extension Poll {
|
||||
typealias Id = String
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ public final class Status: Codable, Identifiable {
|
|||
public static var unknownCase: Self { .unknown }
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let id: Status.Id
|
||||
public let uri: String
|
||||
public let createdAt: Date
|
||||
public let account: Account
|
||||
|
@ -30,8 +30,8 @@ public final class Status: Codable, Identifiable {
|
|||
@DecodableDefault.Zero public private(set) var repliesCount: Int
|
||||
public let application: Application?
|
||||
public let url: URL?
|
||||
public let inReplyToId: String?
|
||||
public let inReplyToAccountId: String?
|
||||
public let inReplyToId: Status.Id?
|
||||
public let inReplyToAccountId: Account.Id?
|
||||
public let reblog: Status?
|
||||
public let poll: Poll?
|
||||
public let card: Card?
|
||||
|
@ -44,7 +44,7 @@ public final class Status: Codable, Identifiable {
|
|||
public let pinned: Bool?
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
id: Status.Id,
|
||||
uri: String,
|
||||
createdAt: Date,
|
||||
account: Account,
|
||||
|
@ -61,8 +61,8 @@ public final class Status: Codable, Identifiable {
|
|||
repliesCount: Int,
|
||||
application: Application?,
|
||||
url: URL?,
|
||||
inReplyToId: String?,
|
||||
inReplyToAccountId: String?,
|
||||
inReplyToId: Status.Id?,
|
||||
inReplyToAccountId: Account.Id?,
|
||||
reblog: Status?,
|
||||
poll: Poll?,
|
||||
card: Card?,
|
||||
|
@ -106,6 +106,8 @@ public final class Status: Codable, Identifiable {
|
|||
}
|
||||
|
||||
public extension Status {
|
||||
typealias Id = String
|
||||
|
||||
var displayStatus: Status {
|
||||
reblog ?? self
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import Mastodon
|
|||
|
||||
public enum AccessTokenEndpoint {
|
||||
case oauthToken(
|
||||
clientID: String,
|
||||
clientId: String,
|
||||
clientSecret: String,
|
||||
grantType: String,
|
||||
scopes: String,
|
||||
|
@ -58,9 +58,9 @@ extension AccessTokenEndpoint: Endpoint {
|
|||
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .oauthToken(clientID, clientSecret, grantType, scopes, code, redirectURI):
|
||||
case let .oauthToken(clientId, clientSecret, grantType, scopes, code, redirectURI):
|
||||
var params = [
|
||||
"client_id": clientID,
|
||||
"client_id": clientId,
|
||||
"client_secret": clientSecret,
|
||||
"grant_type": grantType,
|
||||
"scope": scopes]
|
||||
|
|
|
@ -6,7 +6,7 @@ import Mastodon
|
|||
|
||||
public enum AccountEndpoint {
|
||||
case verifyCredentials
|
||||
case accounts(id: String)
|
||||
case accounts(id: Account.Id)
|
||||
}
|
||||
|
||||
extension AccountEndpoint: Endpoint {
|
||||
|
|
|
@ -5,8 +5,8 @@ import HTTP
|
|||
import Mastodon
|
||||
|
||||
public enum AccountsEndpoint {
|
||||
case statusRebloggedBy(id: String)
|
||||
case statusFavouritedBy(id: String)
|
||||
case rebloggedBy(id: Status.Id)
|
||||
case favouritedBy(id: Status.Id)
|
||||
}
|
||||
|
||||
extension AccountsEndpoint: Endpoint {
|
||||
|
@ -14,23 +14,23 @@ extension AccountsEndpoint: Endpoint {
|
|||
|
||||
public var context: [String] {
|
||||
switch self {
|
||||
case .statusRebloggedBy, .statusFavouritedBy:
|
||||
case .rebloggedBy, .favouritedBy:
|
||||
return defaultContext + ["statuses"]
|
||||
}
|
||||
}
|
||||
|
||||
public var pathComponentsInContext: [String] {
|
||||
switch self {
|
||||
case let .statusRebloggedBy(id):
|
||||
case let .rebloggedBy(id):
|
||||
return [id, "reblogged_by"]
|
||||
case let .statusFavouritedBy(id):
|
||||
case let .favouritedBy(id):
|
||||
return [id, "favourited_by"]
|
||||
}
|
||||
}
|
||||
|
||||
public var method: HTTPMethod {
|
||||
switch self {
|
||||
case .statusRebloggedBy, .statusFavouritedBy:
|
||||
case .rebloggedBy, .favouritedBy:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import HTTP
|
|||
import Mastodon
|
||||
|
||||
public enum ContextEndpoint {
|
||||
case context(id: String)
|
||||
case context(id: Status.Id)
|
||||
}
|
||||
|
||||
extension ContextEndpoint: Endpoint {
|
||||
|
|
|
@ -5,9 +5,9 @@ import HTTP
|
|||
import Mastodon
|
||||
|
||||
public enum DeletionEndpoint {
|
||||
case oauthRevoke(token: String, clientID: String, clientSecret: String)
|
||||
case list(id: String)
|
||||
case filter(id: String)
|
||||
case oauthRevoke(token: String, clientId: String, clientSecret: String)
|
||||
case list(id: List.Id)
|
||||
case filter(id: Filter.Id)
|
||||
}
|
||||
|
||||
extension DeletionEndpoint: Endpoint {
|
||||
|
@ -44,8 +44,8 @@ extension DeletionEndpoint: Endpoint {
|
|||
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .oauthRevoke(token, clientID, clientSecret):
|
||||
return ["token": token, "client_id": clientID, "client_secret": clientSecret]
|
||||
case let .oauthRevoke(token, clientId, clientSecret):
|
||||
return ["token": token, "client_id": clientId, "client_secret": clientSecret]
|
||||
case .list, .filter:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ public enum FilterEndpoint {
|
|||
wholeWord: Bool,
|
||||
expiresIn: Date?)
|
||||
case update(
|
||||
id: String,
|
||||
id: Filter.Id,
|
||||
phrase: String,
|
||||
context: [Filter.Context],
|
||||
irreversible: Bool,
|
||||
|
|
|
@ -6,16 +6,16 @@ import Mastodon
|
|||
|
||||
public struct Paged<T: Endpoint> {
|
||||
public let endpoint: T
|
||||
public let maxID: String?
|
||||
public let minID: String?
|
||||
public let sinceID: String?
|
||||
public let maxId: String?
|
||||
public let minId: String?
|
||||
public let sinceId: String?
|
||||
public let limit: Int?
|
||||
|
||||
public init(_ endpoint: T, maxID: String? = nil, minID: String? = nil, sinceID: String? = nil, limit: Int? = nil) {
|
||||
public init(_ endpoint: T, maxId: String? = nil, minId: String? = nil, sinceId: String? = nil, limit: Int? = nil) {
|
||||
self.endpoint = endpoint
|
||||
self.maxID = maxID
|
||||
self.minID = minID
|
||||
self.sinceID = sinceID
|
||||
self.maxId = maxId
|
||||
self.minId = minId
|
||||
self.sinceId = sinceId
|
||||
self.limit = limit
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ extension Paged: Endpoint {
|
|||
public var queryParameters: [String: String]? {
|
||||
var queryParameters = endpoint.queryParameters ?? [String: String]()
|
||||
|
||||
queryParameters["max_id"] = maxID
|
||||
queryParameters["min_id"] = minID
|
||||
queryParameters["since_id"] = sinceID
|
||||
queryParameters["max_id"] = maxId
|
||||
queryParameters["min_id"] = minId
|
||||
queryParameters["since_id"] = sinceId
|
||||
|
||||
if let limit = limit {
|
||||
queryParameters["limit"] = String(limit)
|
||||
|
@ -50,9 +50,9 @@ extension Paged: Endpoint {
|
|||
|
||||
public struct PagedResult<T: Decodable>: Decodable {
|
||||
public struct Info: Decodable {
|
||||
public let maxID: String?
|
||||
public let minID: String?
|
||||
public let sinceID: String?
|
||||
public let maxId: String?
|
||||
public let minId: String?
|
||||
public let sinceId: String?
|
||||
}
|
||||
|
||||
public let result: T
|
||||
|
|
|
@ -5,9 +5,9 @@ import HTTP
|
|||
import Mastodon
|
||||
|
||||
public enum StatusEndpoint {
|
||||
case status(id: String)
|
||||
case favourite(id: String)
|
||||
case unfavourite(id: String)
|
||||
case status(id: Status.Id)
|
||||
case favourite(id: Status.Id)
|
||||
case unfavourite(id: Status.Id)
|
||||
}
|
||||
|
||||
extension StatusEndpoint: Endpoint {
|
||||
|
|
|
@ -8,8 +8,8 @@ public enum StatusesEndpoint {
|
|||
case timelinesPublic(local: Bool)
|
||||
case timelinesTag(String)
|
||||
case timelinesHome
|
||||
case timelinesList(id: String)
|
||||
case accountsStatuses(id: String, excludeReplies: Bool, onlyMedia: Bool, pinned: Bool)
|
||||
case timelinesList(id: List.Id)
|
||||
case accountsStatuses(id: Account.Id, excludeReplies: Bool, onlyMedia: Bool, pinned: Bool)
|
||||
}
|
||||
|
||||
extension StatusesEndpoint: Endpoint {
|
||||
|
|
|
@ -39,17 +39,17 @@ extension MastodonAPIClient {
|
|||
|
||||
public func pagedRequest<E: Endpoint>(
|
||||
_ endpoint: E,
|
||||
maxID: String? = nil,
|
||||
minID: String? = nil,
|
||||
sinceID: String? = nil,
|
||||
maxId: String? = nil,
|
||||
minId: String? = nil,
|
||||
sinceId: String? = nil,
|
||||
limit: Int? = nil) -> AnyPublisher<PagedResult<E.ResultType>, Error> {
|
||||
let pagedTarget = target(endpoint: Paged(endpoint, maxID: maxID, minID: minID, sinceID: sinceID, limit: limit))
|
||||
let pagedTarget = target(endpoint: Paged(endpoint, maxId: maxId, minId: minId, sinceId: sinceId, limit: limit))
|
||||
let dataTask = dataTaskPublisher(pagedTarget).share()
|
||||
let decoded = dataTask.map(\.data).decode(type: E.ResultType.self, decoder: decoder)
|
||||
let info = dataTask.map { _, response -> PagedResult<E.ResultType>.Info in
|
||||
var maxID: String?
|
||||
var minID: String?
|
||||
var sinceID: String?
|
||||
var maxId: String?
|
||||
var minId: String?
|
||||
var sinceId: String?
|
||||
|
||||
if let links = response.value(forHTTPHeaderField: "Link") {
|
||||
let queryItems = Self.linkDataDetector.matches(
|
||||
|
@ -62,12 +62,12 @@ extension MastodonAPIClient {
|
|||
}
|
||||
.reduce([], +)
|
||||
|
||||
maxID = queryItems.first { $0.name == "max_id" }?.value
|
||||
minID = queryItems.first { $0.name == "min_id" }?.value
|
||||
sinceID = queryItems.first { $0.name == "since_id" }?.value
|
||||
maxId = queryItems.first { $0.name == "max_id" }?.value
|
||||
minId = queryItems.first { $0.name == "min_id" }?.value
|
||||
sinceId = queryItems.first { $0.name == "since_id" }?.value
|
||||
}
|
||||
|
||||
return PagedResult.Info(maxID: maxID, minID: minID, sinceID: sinceID)
|
||||
return PagedResult.Info(maxId: maxId, minId: minId, sinceId: sinceId)
|
||||
}
|
||||
|
||||
return decoded.zip(info).map(PagedResult.init(result:info:)).eraseToAnyPublisher()
|
||||
|
|
|
@ -62,7 +62,7 @@ enum NotificationServiceError: Error {
|
|||
}
|
||||
|
||||
private extension NotificationService {
|
||||
static let identityIDUserInfoKey = "i"
|
||||
static let identityIdUserInfoKey = "i"
|
||||
static let encryptedMessageUserInfoKey = "m"
|
||||
static let saltUserInfoKey = "s"
|
||||
static let serverPublicKeyUserInfoKey = "k"
|
||||
|
@ -82,8 +82,8 @@ private extension NotificationService {
|
|||
|
||||
static func extractAndDecrypt(userInfo: [AnyHashable: Any]) throws -> Data {
|
||||
guard
|
||||
let identityIDString = userInfo[identityIDUserInfoKey] as? String,
|
||||
let identityID = UUID(uuidString: identityIDString),
|
||||
let identityIdString = userInfo[identityIdUserInfoKey] as? String,
|
||||
let identityId = UUID(uuidString: identityIdString),
|
||||
let encryptedMessageBase64 = (userInfo[encryptedMessageUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
|
||||
let encryptedMessage = Data(base64Encoded: encryptedMessageBase64),
|
||||
let saltBase64 = (userInfo[saltUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
|
||||
|
@ -92,7 +92,7 @@ private extension NotificationService {
|
|||
let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64)
|
||||
else { throw NotificationServiceError.userInfoDataAbsent }
|
||||
|
||||
let secretsService = Secrets(identityID: identityID, keychain: LiveKeychain.self)
|
||||
let secretsService = Secrets(identityId: identityId, keychain: LiveKeychain.self)
|
||||
|
||||
guard
|
||||
let auth = try secretsService.getPushAuth(),
|
||||
|
|
|
@ -14,11 +14,11 @@ enum SecretsStorableError: Error {
|
|||
}
|
||||
|
||||
public struct Secrets {
|
||||
public let identityID: UUID
|
||||
public let identityId: UUID
|
||||
private let keychain: Keychain.Type
|
||||
|
||||
public init(identityID: UUID, keychain: Keychain.Type) {
|
||||
self.identityID = identityID
|
||||
public init(identityId: UUID, keychain: Keychain.Type) {
|
||||
self.identityId = identityId
|
||||
self.keychain = keychain
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public struct Secrets {
|
|||
public extension Secrets {
|
||||
enum Item: String, CaseIterable {
|
||||
case instanceURL
|
||||
case clientID
|
||||
case clientId
|
||||
case clientSecret
|
||||
case accessToken
|
||||
case pushKey
|
||||
|
@ -56,12 +56,12 @@ extension Secrets.Item {
|
|||
|
||||
public extension Secrets {
|
||||
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#key
|
||||
static func databaseKey(identityID: UUID?, keychain: Keychain.Type) throws -> String {
|
||||
static func databaseKey(identityId: UUID?, keychain: Keychain.Type) throws -> String {
|
||||
let passphraseData: Data
|
||||
let scopedSecrets: Secrets?
|
||||
|
||||
if let identityID = identityID {
|
||||
scopedSecrets = Secrets(identityID: identityID, keychain: keychain)
|
||||
if let identityId = identityId {
|
||||
scopedSecrets = Secrets(identityId: identityId, keychain: keychain)
|
||||
} else {
|
||||
scopedSecrets = nil
|
||||
}
|
||||
|
@ -114,12 +114,12 @@ public extension Secrets {
|
|||
try set(instanceURL, forItem: .instanceURL)
|
||||
}
|
||||
|
||||
func getClientID() throws -> String {
|
||||
try item(.clientID)
|
||||
func getClientId() throws -> String {
|
||||
try item(.clientId)
|
||||
}
|
||||
|
||||
func setClientID(_ clientID: String) throws {
|
||||
try set(clientID, forItem: .clientID)
|
||||
func setClientId(_ clientId: String) throws {
|
||||
try set(clientId, forItem: .clientId)
|
||||
}
|
||||
|
||||
func getClientSecret() throws -> String {
|
||||
|
@ -200,7 +200,7 @@ private extension Secrets {
|
|||
}
|
||||
|
||||
func scopedKey(item: Item) -> String {
|
||||
identityID.uuidString + "." + item.rawValue
|
||||
identityId.uuidString + "." + item.rawValue
|
||||
}
|
||||
|
||||
func set(_ data: SecretsStorable, forItem item: Item) throws {
|
||||
|
|
|
@ -8,14 +8,14 @@ import MastodonAPI
|
|||
|
||||
public struct AccountListService {
|
||||
public let sections: AnyPublisher<[[CollectionItem]], Error>
|
||||
public let nextPageMaxIDs: AnyPublisher<String, Never>
|
||||
public let nextPageMaxId: AnyPublisher<String, Never>
|
||||
public let navigationService: NavigationService
|
||||
|
||||
private let list: AccountList
|
||||
private let endpoint: AccountsEndpoint
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let contentDatabase: ContentDatabase
|
||||
private let nextPageMaxIDsSubject = PassthroughSubject<String, Never>()
|
||||
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
|
||||
|
||||
init(endpoint: AccountsEndpoint, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
list = AccountList()
|
||||
|
@ -25,18 +25,18 @@ public struct AccountListService {
|
|||
sections = contentDatabase.accountListObservation(list)
|
||||
.map { [$0.map(CollectionItem.account)] }
|
||||
.eraseToAnyPublisher()
|
||||
nextPageMaxIDs = nextPageMaxIDsSubject.eraseToAnyPublisher()
|
||||
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountListService: CollectionService {
|
||||
public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.pagedRequest(endpoint, maxID: maxID, minID: minID)
|
||||
public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.pagedRequest(endpoint, maxId: maxId, minId: minId)
|
||||
.handleEvents(receiveOutput: {
|
||||
guard let maxID = $0.info.maxID else { return }
|
||||
guard let maxId = $0.info.maxId else { return }
|
||||
|
||||
nextPageMaxIDsSubject.send(maxID)
|
||||
nextPageMaxIdSubject.send(maxId)
|
||||
})
|
||||
.flatMap { contentDatabase.append(accounts: $0.result, toList: list) }
|
||||
.eraseToAnyPublisher()
|
||||
|
|
|
@ -8,11 +8,11 @@ import MastodonAPI
|
|||
import Secrets
|
||||
|
||||
public struct AllIdentitiesService {
|
||||
public let identitiesCreated: AnyPublisher<UUID, Never>
|
||||
public let identitiesCreated: AnyPublisher<Identity.Id, Never>
|
||||
|
||||
private let environment: AppEnvironment
|
||||
private let database: IdentityDatabase
|
||||
private let identitiesCreatedSubject = PassthroughSubject<UUID, Never>()
|
||||
private let identitiesCreatedSubject = PassthroughSubject<Identity.Id, Never>()
|
||||
|
||||
public init(environment: AppEnvironment) throws {
|
||||
self.environment = environment
|
||||
|
@ -30,17 +30,17 @@ public extension AllIdentitiesService {
|
|||
case browsing
|
||||
}
|
||||
|
||||
func identityService(id: UUID) throws -> IdentityService {
|
||||
func identityService(id: Identity.Id) throws -> IdentityService {
|
||||
try IdentityService(id: id, database: database, environment: environment)
|
||||
}
|
||||
|
||||
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> {
|
||||
database.immediateMostRecentlyUsedIdentityIDObservation()
|
||||
func immediateMostRecentlyUsedIdentityIdObservation() -> AnyPublisher<Identity.Id?, Error> {
|
||||
database.immediateMostRecentlyUsedIdentityIdObservation()
|
||||
}
|
||||
|
||||
func createIdentity(url: URL, kind: IdentityCreation) -> AnyPublisher<Never, Error> {
|
||||
let id = environment.uuid()
|
||||
let secrets = Secrets(identityID: id, keychain: environment.keychain)
|
||||
let secrets = Secrets(identityId: id, keychain: environment.keychain)
|
||||
|
||||
do {
|
||||
try secrets.setInstanceURL(url)
|
||||
|
@ -76,7 +76,7 @@ public extension AllIdentitiesService {
|
|||
|
||||
return authenticationPublisher
|
||||
.tryMap {
|
||||
try secrets.setClientID($0.clientId)
|
||||
try secrets.setClientId($0.clientId)
|
||||
try secrets.setClientSecret($0.clientSecret)
|
||||
try secrets.setAccessToken($1.accessToken)
|
||||
}
|
||||
|
@ -84,13 +84,13 @@ public extension AllIdentitiesService {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> {
|
||||
func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
||||
database.deleteIdentity(id: id)
|
||||
.collect()
|
||||
.tryMap { _ -> AnyPublisher<Never, Error> in
|
||||
try ContentDatabase.delete(forIdentityID: id)
|
||||
try ContentDatabase.delete(id: id)
|
||||
|
||||
let secrets = Secrets(identityID: id, keychain: environment.keychain)
|
||||
let secrets = Secrets(identityId: id, keychain: environment.keychain)
|
||||
|
||||
defer { secrets.deleteAllItems() }
|
||||
|
||||
|
@ -100,7 +100,7 @@ public extension AllIdentitiesService {
|
|||
instanceURL: try secrets.getInstanceURL())
|
||||
.request(DeletionEndpoint.oauthRevoke(
|
||||
token: try secrets.getAccessToken(),
|
||||
clientID: try secrets.getClientID(),
|
||||
clientId: try secrets.getClientId(),
|
||||
clientSecret: try secrets.getClientSecret()))
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
|
|
|
@ -29,7 +29,8 @@ extension AuthenticationService {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func register(_ registration: Registration, id: UUID) -> AnyPublisher<(AppAuthorization, AccessToken), Error> {
|
||||
func register(_ registration: Registration,
|
||||
id: Identity.Id) -> AnyPublisher<(AppAuthorization, AccessToken), Error> {
|
||||
let redirectURI = OAuth.registrationCallbackURL.appendingPathComponent(id.uuidString)
|
||||
let authorization = appAuthorization(redirectURI: redirectURI)
|
||||
.share()
|
||||
|
@ -38,7 +39,7 @@ extension AuthenticationService {
|
|||
authorization.flatMap { appAuthorization -> AnyPublisher<AccessToken, Error> in
|
||||
mastodonAPIClient.request(
|
||||
AccessTokenEndpoint.oauthToken(
|
||||
clientID: appAuthorization.clientId,
|
||||
clientId: appAuthorization.clientId,
|
||||
clientSecret: appAuthorization.clientSecret,
|
||||
grantType: OAuth.registrationGrantType,
|
||||
scopes: OAuth.scopes,
|
||||
|
@ -134,7 +135,7 @@ private extension AuthenticationService {
|
|||
.flatMap {
|
||||
mastodonAPIClient.request(
|
||||
AccessTokenEndpoint.oauthToken(
|
||||
clientID: appAuthorization.clientId,
|
||||
clientId: appAuthorization.clientId,
|
||||
clientSecret: appAuthorization.clientSecret,
|
||||
grantType: OAuth.authorizationCodeGrantType,
|
||||
scopes: OAuth.scopes,
|
||||
|
|
|
@ -4,17 +4,17 @@ import Combine
|
|||
|
||||
public protocol CollectionService {
|
||||
var sections: AnyPublisher<[[CollectionItem]], Error> { get }
|
||||
var nextPageMaxIDs: AnyPublisher<String, Never> { get }
|
||||
var nextPageMaxId: AnyPublisher<String, Never> { get }
|
||||
var title: AnyPublisher<String, Never> { get }
|
||||
var navigationService: NavigationService { get }
|
||||
var contextParentID: String? { get }
|
||||
func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error>
|
||||
var contextParentId: String? { get }
|
||||
func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error>
|
||||
}
|
||||
|
||||
extension CollectionService {
|
||||
public var nextPageMaxIDs: AnyPublisher<String, Never> { Empty().eraseToAnyPublisher() }
|
||||
public var nextPageMaxId: AnyPublisher<String, Never> { Empty().eraseToAnyPublisher() }
|
||||
|
||||
public var title: AnyPublisher<String, Never> { Empty().eraseToAnyPublisher() }
|
||||
|
||||
public var contextParentID: String? { nil }
|
||||
public var contextParentId: String? { nil }
|
||||
}
|
||||
|
|
|
@ -9,27 +9,27 @@ import MastodonAPI
|
|||
public struct ContextService {
|
||||
public let sections: AnyPublisher<[[CollectionItem]], Error>
|
||||
public let navigationService: NavigationService
|
||||
public var contextParentID: String? { parentID }
|
||||
public var contextParentId: String? { id }
|
||||
|
||||
private let parentID: String
|
||||
private let id: Status.Id
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let contentDatabase: ContentDatabase
|
||||
|
||||
init(parentID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
self.parentID = parentID
|
||||
init(id: Status.Id, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
self.id = id
|
||||
self.mastodonAPIClient = mastodonAPIClient
|
||||
self.contentDatabase = contentDatabase
|
||||
sections = contentDatabase.contextObservation(parentID: parentID)
|
||||
sections = contentDatabase.contextObservation(id: id)
|
||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
extension ContextService: CollectionService {
|
||||
public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(StatusEndpoint.status(id: parentID))
|
||||
public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(StatusEndpoint.status(id: id))
|
||||
.flatMap(contentDatabase.insert(status:))
|
||||
.merge(with: mastodonAPIClient.request(ContextEndpoint.context(id: parentID))
|
||||
.flatMap { contentDatabase.insert(context: $0, parentID: parentID) })
|
||||
.merge(with: mastodonAPIClient.request(ContextEndpoint.context(id: id))
|
||||
.flatMap { contentDatabase.insert(context: $0, parentId: id) })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,25 +9,25 @@ import MastodonAPI
|
|||
import Secrets
|
||||
|
||||
public struct IdentityService {
|
||||
private let identityID: UUID
|
||||
private let id: Identity.Id
|
||||
private let identityDatabase: IdentityDatabase
|
||||
private let contentDatabase: ContentDatabase
|
||||
private let environment: AppEnvironment
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let secrets: Secrets
|
||||
|
||||
init(id: UUID, database: IdentityDatabase, environment: AppEnvironment) throws {
|
||||
identityID = id
|
||||
init(id: Identity.Id, database: IdentityDatabase, environment: AppEnvironment) throws {
|
||||
self.id = id
|
||||
identityDatabase = database
|
||||
self.environment = environment
|
||||
secrets = Secrets(
|
||||
identityID: id,
|
||||
identityId: id,
|
||||
keychain: environment.keychain)
|
||||
mastodonAPIClient = MastodonAPIClient(session: environment.session,
|
||||
instanceURL: try secrets.getInstanceURL())
|
||||
mastodonAPIClient.accessToken = try? secrets.getAccessToken()
|
||||
|
||||
contentDatabase = try ContentDatabase(identityID: id,
|
||||
contentDatabase = try ContentDatabase(id: id,
|
||||
inMemory: environment.inMemoryContent,
|
||||
keychain: environment.keychain)
|
||||
}
|
||||
|
@ -35,29 +35,29 @@ public struct IdentityService {
|
|||
|
||||
public extension IdentityService {
|
||||
func updateLastUse() -> AnyPublisher<Never, Error> {
|
||||
identityDatabase.updateLastUsedAt(identityID: identityID)
|
||||
identityDatabase.updateLastUsedAt(id: id)
|
||||
}
|
||||
|
||||
func verifyCredentials() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(AccountEndpoint.verifyCredentials)
|
||||
.flatMap { identityDatabase.updateAccount($0, forIdentityID: identityID) }
|
||||
.flatMap { identityDatabase.updateAccount($0, id: id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func refreshServerPreferences() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(PreferencesEndpoint.preferences)
|
||||
.flatMap { identityDatabase.updatePreferences($0, forIdentityID: identityID) }
|
||||
.flatMap { identityDatabase.updatePreferences($0, id: id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func refreshInstance() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(InstanceEndpoint.instance)
|
||||
.flatMap { identityDatabase.updateInstance($0, forIdentityID: identityID) }
|
||||
.flatMap { identityDatabase.updateInstance($0, id: id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func confirmIdentity() -> AnyPublisher<Never, Error> {
|
||||
identityDatabase.confirmIdentity(id: identityID)
|
||||
identityDatabase.confirmIdentity(id: id)
|
||||
}
|
||||
|
||||
func identitiesObservation() -> AnyPublisher<[Identity], Error> {
|
||||
|
@ -65,7 +65,7 @@ public extension IdentityService {
|
|||
}
|
||||
|
||||
func recentIdentitiesObservation() -> AnyPublisher<[Identity], Error> {
|
||||
identityDatabase.recentIdentitiesObservation(excluding: identityID)
|
||||
identityDatabase.recentIdentitiesObservation(excluding: id)
|
||||
}
|
||||
|
||||
func refreshLists() -> AnyPublisher<Never, Error> {
|
||||
|
@ -80,7 +80,7 @@ public extension IdentityService {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteList(id: String) -> AnyPublisher<Never, Error> {
|
||||
func deleteList(id: List.Id) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(DeletionEndpoint.list(id: id))
|
||||
.map { _ in id }
|
||||
.flatMap(contentDatabase.deleteList(id:))
|
||||
|
@ -88,7 +88,7 @@ public extension IdentityService {
|
|||
}
|
||||
|
||||
func observation(immediate: Bool) -> AnyPublisher<Identity, Error> {
|
||||
identityDatabase.identityObservation(id: identityID, immediate: immediate)
|
||||
identityDatabase.identityObservation(id: id, immediate: immediate)
|
||||
}
|
||||
|
||||
func listsObservation() -> AnyPublisher<[Timeline], Error> {
|
||||
|
@ -122,7 +122,7 @@ public extension IdentityService {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteFilter(id: String) -> AnyPublisher<Never, Error> {
|
||||
func deleteFilter(id: Filter.Id) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(DeletionEndpoint.filter(id: id))
|
||||
.flatMap { _ in contentDatabase.deleteFilter(id: id) }
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -137,7 +137,7 @@ public extension IdentityService {
|
|||
}
|
||||
|
||||
func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher<Never, Error> {
|
||||
identityDatabase.updatePreferences(preferences, forIdentityID: identityID)
|
||||
identityDatabase.updatePreferences(preferences, id: id)
|
||||
.collect()
|
||||
.filter { _ in preferences.useServerPostingReadingPreferences }
|
||||
.flatMap { _ in refreshServerPreferences() }
|
||||
|
@ -157,7 +157,7 @@ public extension IdentityService {
|
|||
|
||||
let endpoint = Self.pushSubscriptionEndpointURL
|
||||
.appendingPathComponent(deviceToken.base16EncodedString())
|
||||
.appendingPathComponent(identityID.uuidString)
|
||||
.appendingPathComponent(id.uuidString)
|
||||
|
||||
return mastodonAPIClient.request(
|
||||
PushSubscriptionEndpoint.create(
|
||||
|
@ -165,15 +165,15 @@ public extension IdentityService {
|
|||
publicKey: publicKey,
|
||||
auth: auth,
|
||||
alerts: alerts))
|
||||
.map { ($0.alerts, deviceToken, identityID) }
|
||||
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:))
|
||||
.map { ($0.alerts, deviceToken, id) }
|
||||
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:id:))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func updatePushSubscription(alerts: PushSubscription.Alerts) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(PushSubscriptionEndpoint.update(alerts: alerts))
|
||||
.map { ($0.alerts, nil, identityID) }
|
||||
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:))
|
||||
.map { ($0.alerts, nil, id) }
|
||||
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:id:))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ public extension LoadMoreService {
|
|||
func request(direction: LoadMore.Direction) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.pagedRequest(
|
||||
loadMore.timeline.endpoint,
|
||||
maxID: direction == .down ? loadMore.afterStatusId : nil,
|
||||
minID: direction == .up ? loadMore.beforeStatusId : nil)
|
||||
maxId: direction == .down ? loadMore.afterStatusId : nil,
|
||||
minId: direction == .up ? loadMore.beforeStatusId : nil)
|
||||
.flatMap {
|
||||
contentDatabase.insert(
|
||||
statuses: $0.result,
|
||||
|
|
|
@ -36,10 +36,10 @@ public extension NavigationService {
|
|||
mastodonAPIClient: mastodonAPIClient,
|
||||
contentDatabase: contentDatabase)))
|
||||
.eraseToAnyPublisher()
|
||||
} else if let accountID = accountID(url: url) {
|
||||
return Just(.profile(profileService(id: accountID))).eraseToAnyPublisher()
|
||||
} else if mastodonAPIClient.instanceURL.host == url.host, let statusID = url.statusID {
|
||||
return Just(.collection(contextService(id: statusID))).eraseToAnyPublisher()
|
||||
} else if let accountId = accountId(url: url) {
|
||||
return Just(.profile(profileService(id: accountId))).eraseToAnyPublisher()
|
||||
} else if mastodonAPIClient.instanceURL.host == url.host, let statusId = url.statusId {
|
||||
return Just(.collection(contextService(id: statusId))).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
if url.shouldWebfinger {
|
||||
|
@ -49,11 +49,11 @@ public extension NavigationService {
|
|||
}
|
||||
}
|
||||
|
||||
func contextService(id: String) -> ContextService {
|
||||
ContextService(parentID: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
func contextService(id: Status.Id) -> ContextService {
|
||||
ContextService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func profileService(id: String) -> ProfileService {
|
||||
func profileService(id: Account.Id) -> ProfileService {
|
||||
ProfileService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
|
@ -86,12 +86,12 @@ private extension NavigationService {
|
|||
return nil
|
||||
}
|
||||
|
||||
func accountID(url: URL) -> String? {
|
||||
if let mentionID = status?.mentions.first(where: { $0.url.path.lowercased() == url.path.lowercased() })?.id {
|
||||
return mentionID
|
||||
func accountId(url: URL) -> String? {
|
||||
if let mentionId = status?.mentions.first(where: { $0.url.path.lowercased() == url.path.lowercased() })?.id {
|
||||
return mentionId
|
||||
} else if
|
||||
mastodonAPIClient.instanceURL.host == url.host {
|
||||
return url.accountID
|
||||
return url.accountId
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -131,21 +131,21 @@ private extension URL {
|
|||
|| (pathComponents.count == 3 && pathComponents[0...1] == ["/", "users"])
|
||||
}
|
||||
|
||||
var accountID: String? {
|
||||
if let accountID = pathComponents.last, pathComponents == ["/", "web", "accounts", accountID] {
|
||||
return accountID
|
||||
var accountId: Account.Id? {
|
||||
if let accountId = pathComponents.last, pathComponents == ["/", "web", "accounts", accountId] {
|
||||
return accountId
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var statusID: String? {
|
||||
guard let statusID = pathComponents.last else { return nil }
|
||||
var statusId: Status.Id? {
|
||||
guard let statusId = pathComponents.last else { return nil }
|
||||
|
||||
if pathComponents.count == 3, pathComponents[1].starts(with: "@") {
|
||||
return statusID
|
||||
} else if pathComponents == ["/", "web", "statuses", statusID] {
|
||||
return statusID
|
||||
return statusId
|
||||
} else if pathComponents == ["/", "web", "statuses", statusId] {
|
||||
return statusId
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -160,6 +160,6 @@ private extension URL {
|
|||
}
|
||||
|
||||
var shouldWebfinger: Bool {
|
||||
isAccountURL || accountID != nil || statusID != nil || tag != nil
|
||||
isAccountURL || accountId != nil || statusId != nil || tag != nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import MastodonAPI
|
|||
public struct ProfileService {
|
||||
public let accountServicePublisher: AnyPublisher<AccountService, Error>
|
||||
|
||||
private let accountID: String
|
||||
private let id: Account.Id
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let contentDatabase: ContentDatabase
|
||||
|
||||
|
@ -21,20 +21,20 @@ public struct ProfileService {
|
|||
contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
init(id: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
init(id: Account.Id, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
self.init(id: id, account: nil, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
private init(
|
||||
id: String,
|
||||
id: Account.Id,
|
||||
account: Account?,
|
||||
mastodonAPIClient: MastodonAPIClient,
|
||||
contentDatabase: ContentDatabase) {
|
||||
accountID = id
|
||||
self.id = id
|
||||
self.mastodonAPIClient = mastodonAPIClient
|
||||
self.contentDatabase = contentDatabase
|
||||
|
||||
var accountPublisher = contentDatabase.accountObservation(id: accountID)
|
||||
var accountPublisher = contentDatabase.accountObservation(id: id)
|
||||
|
||||
if let account = account {
|
||||
accountPublisher = accountPublisher
|
||||
|
@ -52,7 +52,7 @@ public struct ProfileService {
|
|||
public extension ProfileService {
|
||||
func timelineService(profileCollection: ProfileCollection) -> TimelineService {
|
||||
TimelineService(
|
||||
timeline: .profile(accountId: accountID, profileCollection: profileCollection),
|
||||
timeline: .profile(accountId: id, profileCollection: profileCollection),
|
||||
mastodonAPIClient: mastodonAPIClient,
|
||||
contentDatabase: contentDatabase)
|
||||
}
|
||||
|
@ -60,11 +60,11 @@ public extension ProfileService {
|
|||
func fetchPinnedStatuses() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(
|
||||
StatusesEndpoint.accountsStatuses(
|
||||
id: accountID,
|
||||
id: id,
|
||||
excludeReplies: true,
|
||||
onlyMedia: false,
|
||||
pinned: true))
|
||||
.flatMap { contentDatabase.insert(pinnedStatuses: $0, accountID: accountID) }
|
||||
.flatMap { contentDatabase.insert(pinnedStatuses: $0, accountId: id) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,14 +34,14 @@ public extension StatusService {
|
|||
|
||||
func rebloggedByService() -> AccountListService {
|
||||
AccountListService(
|
||||
endpoint: .statusRebloggedBy(id: status.id),
|
||||
endpoint: .rebloggedBy(id: status.id),
|
||||
mastodonAPIClient: mastodonAPIClient,
|
||||
contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func favoritedByService() -> AccountListService {
|
||||
AccountListService(
|
||||
endpoint: .statusFavouritedBy(id: status.id),
|
||||
endpoint: .favouritedBy(id: status.id),
|
||||
mastodonAPIClient: mastodonAPIClient,
|
||||
contentDatabase: contentDatabase)
|
||||
}
|
||||
|
|
|
@ -9,14 +9,13 @@ import MastodonAPI
|
|||
public struct TimelineService {
|
||||
public let sections: AnyPublisher<[[CollectionItem]], Error>
|
||||
public let navigationService: NavigationService
|
||||
public let nextPageMaxIDs: AnyPublisher<String, Never>
|
||||
public let nextPageMaxId: AnyPublisher<String, Never>
|
||||
public let title: AnyPublisher<String, Never>
|
||||
public let contextParentID: String? = nil
|
||||
|
||||
private let timeline: Timeline
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let contentDatabase: ContentDatabase
|
||||
private let nextPageMaxIDsSubject = PassthroughSubject<String, Never>()
|
||||
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
|
||||
|
||||
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
self.timeline = timeline
|
||||
|
@ -24,7 +23,7 @@ public struct TimelineService {
|
|||
self.contentDatabase = contentDatabase
|
||||
sections = contentDatabase.observation(timeline: timeline)
|
||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
nextPageMaxIDs = nextPageMaxIDsSubject.eraseToAnyPublisher()
|
||||
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
||||
|
||||
if case let .tag(tag) = timeline {
|
||||
title = Just("#".appending(tag)).eraseToAnyPublisher()
|
||||
|
@ -35,12 +34,12 @@ public struct TimelineService {
|
|||
}
|
||||
|
||||
extension TimelineService: CollectionService {
|
||||
public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.pagedRequest(timeline.endpoint, maxID: maxID, minID: minID)
|
||||
public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.pagedRequest(timeline.endpoint, maxId: maxId, minId: minId)
|
||||
.handleEvents(receiveOutput: {
|
||||
guard let maxID = $0.info.maxID else { return }
|
||||
guard let maxId = $0.info.maxId else { return }
|
||||
|
||||
nextPageMaxIDsSubject.send(maxID)
|
||||
nextPageMaxIdSubject.send(maxId)
|
||||
})
|
||||
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
|
||||
.eraseToAnyPublisher()
|
||||
|
|
|
@ -42,7 +42,7 @@ class InstanceURLServiceTests: XCTestCase {
|
|||
updatedFilter.insert("instance.filtered")
|
||||
|
||||
let updatedFilterData = try JSONEncoder().encode(updatedFilter)
|
||||
let stub: HTTPStub = .success((URLResponse(), updatedFilterData))
|
||||
let stub: HTTPStub = .success((HTTPURLResponse(), updatedFilterData))
|
||||
|
||||
StubbingURLProtocol.setStub(stub, forURL: URL(string: "https://filter.metabolist.com/filter")!)
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ class TableViewController: UITableViewController {
|
|||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
viewModel.request(maxID: nil, minID: nil)
|
||||
viewModel.request(maxId: nil, minId: nil)
|
||||
}
|
||||
|
||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
@ -130,13 +130,13 @@ class TableViewController: UITableViewController {
|
|||
extension TableViewController: UITableViewDataSourcePrefetching {
|
||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||
guard
|
||||
let maxID = viewModel.nextPageMaxID,
|
||||
let maxId = viewModel.nextPageMaxId,
|
||||
let indexPath = indexPaths.last,
|
||||
indexPath.section == dataSource.numberOfSections(in: tableView) - 1,
|
||||
indexPath.row == dataSource.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1
|
||||
else { return }
|
||||
|
||||
viewModel.request(maxID: maxID, minID: nil)
|
||||
viewModel.request(maxId: maxId, minId: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@ import ViewModels
|
|||
// swiftlint:disable force_try
|
||||
|
||||
let db: IdentityDatabase = {
|
||||
let id = UUID()
|
||||
let id = Identity.Id()
|
||||
let db = try! IdentityDatabase(inMemory: true, keychain: MockKeychain.self)
|
||||
let secrets = Secrets(identityID: id, keychain: MockKeychain.self)
|
||||
let secrets = Secrets(identityId: id, keychain: MockKeychain.self)
|
||||
|
||||
try! secrets.setInstanceURL(.previewInstanceURL)
|
||||
try! secrets.setAccessToken(UUID().uuidString)
|
||||
|
@ -25,11 +25,11 @@ let db: IdentityDatabase = {
|
|||
.receive(on: ImmediateScheduler.shared)
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
|
||||
_ = db.updateInstance(.preview, forIdentityID: id)
|
||||
_ = db.updateInstance(.preview, id: id)
|
||||
.receive(on: ImmediateScheduler.shared)
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
|
||||
_ = db.updateAccount(.preview, forIdentityID: id)
|
||||
_ = db.updateAccount(.preview, id: id)
|
||||
.receive(on: ImmediateScheduler.shared)
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import ServiceLayer
|
|||
|
||||
final public class CollectionItemsViewModel: ObservableObject {
|
||||
@Published public var alertItem: AlertItem?
|
||||
public private(set) var nextPageMaxID: String?
|
||||
public private(set) var nextPageMaxId: String?
|
||||
public private(set) var maintainScrollPositionOfItem: CollectionItemIdentifier?
|
||||
|
||||
private let items = CurrentValueSubject<[[CollectionItem]], Never>([])
|
||||
|
@ -27,8 +27,8 @@ final public class CollectionItemsViewModel: ObservableObject {
|
|||
.sink { _ in }
|
||||
.store(in: &cancellables)
|
||||
|
||||
collectionService.nextPageMaxIDs
|
||||
.sink { [weak self] in self?.nextPageMaxID = $0 }
|
||||
collectionService.nextPageMaxId
|
||||
.sink { [weak self] in self?.nextPageMaxId = $0 }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +46,8 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
|||
|
||||
public var navigationEvents: AnyPublisher<NavigationEvent, Never> { navigationEventsSubject.eraseToAnyPublisher() }
|
||||
|
||||
public func request(maxID: String? = nil, minID: String? = nil) {
|
||||
collectionService.request(maxID: maxID, minID: minID)
|
||||
public func request(maxId: String? = nil, minId: String? = nil) {
|
||||
collectionService.request(maxId: maxId, minId: minId)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.handleEvents(
|
||||
|
@ -80,7 +80,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
|||
|
||||
public func canSelect(indexPath: IndexPath) -> Bool {
|
||||
if case let .status(configuration) = items.value[indexPath.section][indexPath.item],
|
||||
configuration.status.id == collectionService.contextParentID {
|
||||
configuration.status.id == collectionService.contextParentId {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
|||
cache(viewModel: viewModel, forItem: item)
|
||||
}
|
||||
|
||||
viewModel.isContextParent = configuration.status.id == collectionService.contextParentID
|
||||
viewModel.isContextParent = configuration.status.id == collectionService.contextParentId
|
||||
viewModel.isPinned = configuration.pinned
|
||||
viewModel.isReplyInContext = configuration.isReplyInContext
|
||||
viewModel.hasReplyFollowing = configuration.hasReplyFollowing
|
||||
|
@ -154,12 +154,12 @@ private extension CollectionItemsViewModel {
|
|||
maintainScrollPositionOfItem = nil // clear old value
|
||||
|
||||
// Maintain scroll position of parent after initial load of context
|
||||
if let contextParentID = collectionService.contextParentID {
|
||||
let contextParentIdentifier = CollectionItemIdentifier(id: contextParentID, kind: .status, info: [:])
|
||||
let onlyContextParentID = [[], [contextParentIdentifier], []]
|
||||
if let contextParentId = collectionService.contextParentId {
|
||||
let contextParentIdentifier = CollectionItemIdentifier(id: contextParentId, kind: .status, info: [:])
|
||||
let onlyContextParentId = [[], [contextParentIdentifier], []]
|
||||
|
||||
if items.value.isEmpty
|
||||
|| items.value.map({ $0.map(CollectionItemIdentifier.init(item:)) }) == onlyContextParentID {
|
||||
|| items.value.map({ $0.map(CollectionItemIdentifier.init(item:)) }) == onlyContextParentId {
|
||||
maintainScrollPositionOfItem = contextParentIdentifier
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ public protocol CollectionViewModel {
|
|||
var alertItems: AnyPublisher<AlertItem, Never> { get }
|
||||
var loading: AnyPublisher<Bool, Never> { get }
|
||||
var navigationEvents: AnyPublisher<NavigationEvent, Never> { get }
|
||||
var nextPageMaxID: String? { get }
|
||||
var nextPageMaxId: String? { get }
|
||||
var maintainScrollPositionOfItem: CollectionItemIdentifier? { get }
|
||||
func request(maxID: String?, minID: String?)
|
||||
func request(maxId: String?, minId: String?)
|
||||
func select(indexPath: IndexPath)
|
||||
func canSelect(indexPath: IndexPath) -> Bool
|
||||
func viewModel(indexPath: IndexPath) -> CollectionItemViewModel
|
||||
|
|
|
@ -28,7 +28,7 @@ public final class EditFilterViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
public extension EditFilterViewModel {
|
||||
var isNew: Bool { filter.id == Filter.newFilterID }
|
||||
var isNew: Bool { filter.id == Filter.newFilterId }
|
||||
|
||||
var isSaveDisabled: Bool { filter.phrase == "" || filter.context.isEmpty }
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import Foundation
|
|||
import ServiceLayer
|
||||
|
||||
public final class IdentitiesViewModel: ObservableObject {
|
||||
public let currentIdentityID: UUID
|
||||
public let currentIdentityId: Identity.Id
|
||||
@Published public var authenticated = [Identity]()
|
||||
@Published public var unauthenticated = [Identity]()
|
||||
@Published public var pending = [Identity]()
|
||||
|
@ -16,7 +16,7 @@ public final class IdentitiesViewModel: ObservableObject {
|
|||
|
||||
public init(identification: Identification) {
|
||||
self.identification = identification
|
||||
currentIdentityID = identification.identity.id
|
||||
currentIdentityId = identification.identity.id
|
||||
|
||||
let observation = identification.service.identitiesObservation()
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
|
|
|
@ -66,23 +66,23 @@ extension ProfileViewModel: CollectionViewModel {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public var nextPageMaxID: String? {
|
||||
collectionViewModel.value.nextPageMaxID
|
||||
public var nextPageMaxId: String? {
|
||||
collectionViewModel.value.nextPageMaxId
|
||||
}
|
||||
|
||||
public var maintainScrollPositionOfItem: CollectionItemIdentifier? {
|
||||
collectionViewModel.value.maintainScrollPositionOfItem
|
||||
}
|
||||
|
||||
public func request(maxID: String?, minID: String?) {
|
||||
if case .statuses = collection, maxID == nil {
|
||||
public func request(maxId: String?, minId: String?) {
|
||||
if case .statuses = collection, maxId == nil {
|
||||
profileService.fetchPinnedStatuses()
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.sink { _ in }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
collectionViewModel.value.request(maxID: maxID, minID: minID)
|
||||
collectionViewModel.value.request(maxId: maxId, minId: minId)
|
||||
}
|
||||
|
||||
public func select(indexPath: IndexPath) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import ServiceLayer
|
|||
public final class RootViewModel: ObservableObject {
|
||||
@Published public private(set) var navigationViewModel: NavigationViewModel?
|
||||
|
||||
@Published private var mostRecentlyUsedIdentityID: UUID?
|
||||
@Published private var mostRecentlyUsedIdentityId: Identity.Id?
|
||||
private let environment: AppEnvironment
|
||||
private let allIdentitiesService: AllIdentitiesService
|
||||
private let userNotificationService: UserNotificationService
|
||||
|
@ -21,11 +21,11 @@ public final class RootViewModel: ObservableObject {
|
|||
userNotificationService = UserNotificationService(environment: environment)
|
||||
self.registerForRemoteNotifications = registerForRemoteNotifications
|
||||
|
||||
allIdentitiesService.immediateMostRecentlyUsedIdentityIDObservation()
|
||||
allIdentitiesService.immediateMostRecentlyUsedIdentityIdObservation()
|
||||
.replaceError(with: nil)
|
||||
.assign(to: &$mostRecentlyUsedIdentityID)
|
||||
.assign(to: &$mostRecentlyUsedIdentityId)
|
||||
|
||||
identitySelected(id: mostRecentlyUsedIdentityID, immediate: true)
|
||||
identitySelected(id: mostRecentlyUsedIdentityId, immediate: true)
|
||||
|
||||
allIdentitiesService.identitiesCreated
|
||||
.sink { [weak self] in self?.identitySelected(id: $0) }
|
||||
|
@ -42,11 +42,11 @@ public final class RootViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
public extension RootViewModel {
|
||||
func identitySelected(id: UUID?) {
|
||||
func identitySelected(id: Identity.Id?) {
|
||||
identitySelected(id: id, immediate: false)
|
||||
}
|
||||
|
||||
func deleteIdentity(id: UUID) {
|
||||
func deleteIdentity(id: Identity.Id) {
|
||||
allIdentitiesService.deleteIdentity(id: id)
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
.store(in: &cancellables)
|
||||
|
@ -60,7 +60,7 @@ public extension RootViewModel {
|
|||
}
|
||||
|
||||
private extension RootViewModel {
|
||||
func identitySelected(id: UUID?, immediate: Bool) {
|
||||
func identitySelected(id: Identity.Id?, immediate: Bool) {
|
||||
navigationViewModel?.presentingSecondaryNavigation = false
|
||||
|
||||
guard
|
||||
|
@ -74,7 +74,7 @@ private extension RootViewModel {
|
|||
let observation = identityService.observation(immediate: immediate)
|
||||
.catch { [weak self] _ -> Empty<Identity, Never> in
|
||||
DispatchQueue.main.async {
|
||||
self?.identitySelected(id: self?.mostRecentlyUsedIdentityID, immediate: false)
|
||||
self?.identitySelected(id: self?.mostRecentlyUsedIdentityId, immediate: false)
|
||||
}
|
||||
|
||||
return Empty()
|
||||
|
|
|
@ -18,12 +18,12 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
let sut = AddIdentityViewModel(
|
||||
allIdentitiesService: allIdentitiesService,
|
||||
instanceURLService: InstanceURLService(environment: environment))
|
||||
let addedIDRecorder = allIdentitiesService.identitiesCreated.record()
|
||||
let addedIdRecorder = allIdentitiesService.identitiesCreated.record()
|
||||
|
||||
sut.urlFieldText = "https://mastodon.social"
|
||||
sut.logInTapped()
|
||||
|
||||
_ = try wait(for: addedIDRecorder.next(), timeout: 1)
|
||||
_ = try wait(for: addedIdRecorder.next(), timeout: 1)
|
||||
}
|
||||
|
||||
func testAddIdentityWithoutScheme() throws {
|
||||
|
@ -33,12 +33,12 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
let sut = AddIdentityViewModel(
|
||||
allIdentitiesService: allIdentitiesService,
|
||||
instanceURLService: InstanceURLService(environment: environment))
|
||||
let addedIDRecorder = allIdentitiesService.identitiesCreated.record()
|
||||
let addedIdRecorder = allIdentitiesService.identitiesCreated.record()
|
||||
|
||||
sut.urlFieldText = "mastodon.social"
|
||||
sut.logInTapped()
|
||||
|
||||
_ = try wait(for: addedIDRecorder.next(), timeout: 1)
|
||||
_ = try wait(for: addedIdRecorder.next(), timeout: 1)
|
||||
}
|
||||
|
||||
func testInvalidURL() throws {
|
||||
|
|
|
@ -81,7 +81,7 @@ private extension AccountHeaderView {
|
|||
segmentedControl.insertSegment(
|
||||
action: UIAction(title: collection.title) { [weak self] _ in
|
||||
self?.viewModel?.collection = collection
|
||||
self?.viewModel?.request(maxID: nil, minID: nil)
|
||||
self?.viewModel?.request(maxId: nil, minId: nil)
|
||||
},
|
||||
at: index,
|
||||
animated: false)
|
||||
|
|
|
@ -47,7 +47,7 @@ private extension IdentitiesView {
|
|||
} label: {
|
||||
row(identity: identity)
|
||||
}
|
||||
.disabled(identity.id == viewModel.currentIdentityID)
|
||||
.disabled(identity.id == viewModel.currentIdentityId)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.onDelete {
|
||||
|
@ -94,7 +94,7 @@ private extension IdentitiesView {
|
|||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
if identity.id == viewModel.currentIdentityID {
|
||||
if identity.id == viewModel.currentIdentityId {
|
||||
Image(systemName: "checkmark.circle")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue