The great id cleanup

This commit is contained in:
Justin Mazzocchi 2020-10-05 15:50:05 -07:00
parent 9ac6ed2d93
commit 15d6e10edc
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
63 changed files with 345 additions and 309 deletions

View File

@ -1,5 +1,6 @@
disabled_rules: disabled_rules:
- identifier_name - identifier_name
- type_name
# Swift 5.3 # Swift 5.3
- multiple_closures_with_trailing_closure - multiple_closures_with_trailing_closure
- no_space_in_method_call - no_space_in_method_call

View File

@ -4,13 +4,17 @@ import Foundation
import GRDB import GRDB
public struct AccountList: Codable, FetchableRecord, PersistableRecord { public struct AccountList: Codable, FetchableRecord, PersistableRecord {
let id: UUID let id: Id
public init() { public init() {
id = UUID() id = Id()
} }
} }
public extension AccountList {
typealias Id = UUID
}
extension AccountList { extension AccountList {
static let joins = hasMany(AccountListJoin.self).order(AccountListJoin.Columns.index) static let joins = hasMany(AccountListJoin.self).order(AccountListJoin.Columns.index)
static let accounts = hasMany( static let accounts = hasMany(

View File

@ -2,10 +2,11 @@
import Foundation import Foundation
import GRDB import GRDB
import Mastodon
struct AccountListJoin: Codable, FetchableRecord, PersistableRecord { struct AccountListJoin: Codable, FetchableRecord, PersistableRecord {
let accountId: String let accountId: Account.Id
let listId: UUID let listId: AccountList.Id
let index: Int let index: Int
static let account = belongsTo(AccountRecord.self) static let account = belongsTo(AccountRecord.self)

View File

@ -2,10 +2,11 @@
import Foundation import Foundation
import GRDB import GRDB
import Mastodon
struct AccountPinnedStatusJoin: Codable, FetchableRecord, PersistableRecord { struct AccountPinnedStatusJoin: Codable, FetchableRecord, PersistableRecord {
let accountId: String let accountId: Account.Id
let statusId: String let statusId: Status.Id
let index: Int let index: Int
} }

View File

@ -5,7 +5,7 @@ import GRDB
import Mastodon import Mastodon
struct AccountRecord: Codable, Hashable { struct AccountRecord: Codable, Hashable {
let id: String let id: Account.Id
let username: String let username: String
let acct: String let acct: String
let displayName: String let displayName: String
@ -24,7 +24,7 @@ struct AccountRecord: Codable, Hashable {
let emojis: [Emoji] let emojis: [Emoji]
let bot: Bool let bot: Bool
let discoverable: Bool let discoverable: Bool
let movedId: String? let movedId: Account.Id?
} }
extension AccountRecord { extension AccountRecord {

View File

@ -12,15 +12,15 @@ public struct ContentDatabase {
private let databaseWriter: DatabaseWriter 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 { if inMemory {
databaseWriter = DatabaseQueue() databaseWriter = DatabaseQueue()
} else { } else {
let path = try Self.fileURL(identityID: identityID).path let path = try Self.fileURL(id: id).path
var configuration = Configuration() var configuration = Configuration()
configuration.prepareDatabase { 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) databaseWriter = try DatabasePool(path: path, configuration: configuration)
@ -39,8 +39,8 @@ public struct ContentDatabase {
} }
public extension ContentDatabase { public extension ContentDatabase {
static func delete(forIdentityID identityID: UUID) throws { static func delete(id: Identity.Id) throws {
try FileManager.default.removeItem(at: fileURL(identityID: identityID)) try FileManager.default.removeItem(at: fileURL(id: id))
} }
func insert(status: Status) -> AnyPublisher<Never, Error> { func insert(status: Status) -> AnyPublisher<Never, Error> {
@ -58,7 +58,7 @@ public extension ContentDatabase {
try timelineRecord.save($0) 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 { for status in statuses {
try status.save($0) try status.save($0)
@ -66,13 +66,13 @@ public extension ContentDatabase {
try TimelineStatusJoin(timelineId: timeline.id, statusId: status.id).save($0) try TimelineStatusJoin(timelineId: timeline.id, statusId: status.id).save($0)
} }
if let maxIDPresent = maxIDPresent, if let maxIdPresent = maxIdPresent,
let minIDInserted = statuses.map(\.id).min(), let minIdInserted = statuses.map(\.id).min(),
minIDInserted > maxIDPresent { minIdInserted > maxIdPresent {
try LoadMoreRecord( try LoadMoreRecord(
timelineId: timeline.id, timelineId: timeline.id,
afterStatusId: minIDInserted, afterStatusId: minIdInserted,
beforeStatusId: maxIDPresent) beforeStatusId: maxIdPresent)
.save($0) .save($0)
} }
@ -86,18 +86,18 @@ public extension ContentDatabase {
switch direction { switch direction {
case .up: case .up:
if let maxIDInserted = statuses.map(\.id).max(), maxIDInserted < loadMore.afterStatusId { if let maxIdInserted = statuses.map(\.id).max(), maxIdInserted < loadMore.afterStatusId {
try LoadMoreRecord( try LoadMoreRecord(
timelineId: loadMore.timeline.id, timelineId: loadMore.timeline.id,
afterStatusId: loadMore.afterStatusId, afterStatusId: loadMore.afterStatusId,
beforeStatusId: maxIDInserted) beforeStatusId: maxIdInserted)
.save($0) .save($0)
} }
case .down: case .down:
if let minIDInserted = statuses.map(\.id).min(), minIDInserted > loadMore.beforeStatusId { if let minIdInserted = statuses.map(\.id).min(), minIdInserted > loadMore.beforeStatusId {
try LoadMoreRecord( try LoadMoreRecord(
timelineId: loadMore.timeline.id, timelineId: loadMore.timeline.id,
afterStatusId: minIDInserted, afterStatusId: minIdInserted,
beforeStatusId: loadMore.beforeStatusId) beforeStatusId: loadMore.beforeStatusId)
.save($0) .save($0)
} }
@ -107,25 +107,25 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func insert(context: Context, parentID: String) -> AnyPublisher<Never, Error> { func insert(context: Context, parentId: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
for (index, status) in context.ancestors.enumerated() { for (index, status) in context.ancestors.enumerated() {
try status.save($0) 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() { for (index, status) in context.descendants.enumerated() {
try status.save($0) 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( try StatusAncestorJoin.filter(
StatusAncestorJoin.Columns.parentId == parentID StatusAncestorJoin.Columns.parentId == parentId
&& !context.ancestors.map(\.id).contains(StatusAncestorJoin.Columns.statusId)) && !context.ancestors.map(\.id).contains(StatusAncestorJoin.Columns.statusId))
.deleteAll($0) .deleteAll($0)
try StatusDescendantJoin.filter( try StatusDescendantJoin.filter(
StatusDescendantJoin.Columns.parentId == parentID StatusDescendantJoin.Columns.parentId == parentId
&& !context.descendants.map(\.id).contains(StatusDescendantJoin.Columns.statusId)) && !context.descendants.map(\.id).contains(StatusDescendantJoin.Columns.statusId))
.deleteAll($0) .deleteAll($0)
} }
@ -133,15 +133,15 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func insert(pinnedStatuses: [Status], accountID: String) -> AnyPublisher<Never, Error> { func insert(pinnedStatuses: [Status], accountId: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
for (index, status) in pinnedStatuses.enumerated() { for (index, status) in pinnedStatuses.enumerated() {
try status.save($0) 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( try AccountPinnedStatusJoin.filter(
AccountPinnedStatusJoin.Columns.accountId == accountID AccountPinnedStatusJoin.Columns.accountId == accountId
&& !pinnedStatuses.map(\.id).contains(AccountPinnedStatusJoin.Columns.statusId)) && !pinnedStatuses.map(\.id).contains(AccountPinnedStatusJoin.Columns.statusId))
.deleteAll($0) .deleteAll($0)
} }
@ -185,7 +185,7 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .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) databaseWriter.writePublisher(updates: TimelineRecord.filter(TimelineRecord.Columns.listId == id).deleteAll)
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
@ -209,7 +209,7 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .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) databaseWriter.writePublisher(updates: Filter.filter(Filter.Columns.id == id).deleteAll)
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
@ -225,9 +225,9 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func contextObservation(parentID: String) -> AnyPublisher<[[CollectionItem]], Error> { func contextObservation(id: Status.Id) -> AnyPublisher<[[CollectionItem]], Error> {
ValueObservation.tracking( ValueObservation.tracking(
ContextItemsInfo.request(StatusRecord.filter(StatusRecord.Columns.id == parentID)).fetchOne) ContextItemsInfo.request(StatusRecord.filter(StatusRecord.Columns.id == id)).fetchOne)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseWriter) .publisher(in: databaseWriter)
.combineLatest(activeFiltersPublisher) .combineLatest(activeFiltersPublisher)
@ -252,7 +252,7 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .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) ValueObservation.tracking(AccountInfo.request(AccountRecord.filter(AccountRecord.Columns.id == id)).fetchOne)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseWriter) .publisher(in: databaseWriter)
@ -271,8 +271,8 @@ public extension ContentDatabase {
} }
private extension ContentDatabase { private extension ContentDatabase {
static func fileURL(identityID: UUID) throws -> URL { static func fileURL(id: Identity.Id) throws -> URL {
try FileManager.default.databaseDirectoryURL(name: identityID.uuidString) try FileManager.default.databaseDirectoryURL(name: id.uuidString)
} }
static func clean(_ databaseWriter: DatabaseWriter) throws { static func clean(_ databaseWriter: DatabaseWriter) throws {

View File

@ -5,9 +5,9 @@ import GRDB
import Mastodon import Mastodon
struct LoadMoreRecord: Codable, Hashable { struct LoadMoreRecord: Codable, Hashable {
let timelineId: String let timelineId: Timeline.Id
let afterStatusId: String let afterStatusId: Status.Id
let beforeStatusId: String let beforeStatusId: Status.Id
} }
extension LoadMoreRecord { extension LoadMoreRecord {

View File

@ -2,10 +2,11 @@
import Foundation import Foundation
import GRDB import GRDB
import Mastodon
struct StatusAncestorJoin: Codable, FetchableRecord, PersistableRecord { struct StatusAncestorJoin: Codable, FetchableRecord, PersistableRecord {
let parentId: String let parentId: Status.Id
let statusId: String let statusId: Status.Id
let index: Int let index: Int
static let status = belongsTo(StatusRecord.self, using: ForeignKey([Columns.statusId])) static let status = belongsTo(StatusRecord.self, using: ForeignKey([Columns.statusId]))

View File

@ -2,10 +2,11 @@
import Foundation import Foundation
import GRDB import GRDB
import Mastodon
struct StatusDescendantJoin: Codable, FetchableRecord, PersistableRecord { struct StatusDescendantJoin: Codable, FetchableRecord, PersistableRecord {
let parentId: String let parentId: Status.Id
let statusId: String let statusId: Status.Id
let index: Int let index: Int
static let status = belongsTo(StatusRecord.self, using: ForeignKey([Columns.statusId])) static let status = belongsTo(StatusRecord.self, using: ForeignKey([Columns.statusId]))

View File

@ -5,10 +5,10 @@ import GRDB
import Mastodon import Mastodon
struct StatusRecord: Codable, Hashable { struct StatusRecord: Codable, Hashable {
let id: String let id: Status.Id
let uri: String let uri: String
let createdAt: Date let createdAt: Date
let accountId: String let accountId: Account.Id
let content: HTML let content: HTML
let visibility: Status.Visibility let visibility: Status.Visibility
let sensitive: Bool let sensitive: Bool
@ -22,9 +22,9 @@ struct StatusRecord: Codable, Hashable {
let repliesCount: Int let repliesCount: Int
let application: Application? let application: Application?
let url: URL? let url: URL?
let inReplyToId: String? let inReplyToId: Status.Id?
let inReplyToAccountId: String? let inReplyToAccountId: Account.Id?
let reblogId: String? let reblogId: Status.Id?
let poll: Poll? let poll: Poll?
let card: Card? let card: Card?
let language: String? let language: String?

View File

@ -5,11 +5,11 @@ import GRDB
import Mastodon import Mastodon
struct TimelineRecord: Codable, Hashable { struct TimelineRecord: Codable, Hashable {
let id: String let id: Timeline.Id
let listId: String? let listId: List.Id?
let listTitle: String? let listTitle: String?
let tag: String? let tag: String?
let accountId: String? let accountId: Account.Id?
let profileCollection: ProfileCollection? let profileCollection: ProfileCollection?
} }

View File

@ -2,10 +2,11 @@
import Foundation import Foundation
import GRDB import GRDB
import Mastodon
struct TimelineStatusJoin: Codable, FetchableRecord, PersistableRecord { struct TimelineStatusJoin: Codable, FetchableRecord, PersistableRecord {
let timelineId: String let timelineId: Timeline.Id
let statusId: String let statusId: Status.Id
static let status = belongsTo(StatusRecord.self) static let status = belongsTo(StatusRecord.self)
} }

View File

@ -4,7 +4,7 @@ import Foundation
import Mastodon import Mastodon
public struct Identity: Codable, Hashable, Identifiable { public struct Identity: Codable, Hashable, Identifiable {
public let id: UUID public let id: Id
public let url: URL public let url: URL
public let authenticated: Bool public let authenticated: Bool
public let pending: Bool public let pending: Bool
@ -17,6 +17,8 @@ public struct Identity: Codable, Hashable, Identifiable {
} }
public extension Identity { public extension Identity {
typealias Id = UUID
struct Instance: Codable, Hashable { struct Instance: Codable, Hashable {
public let uri: String public let uri: String
public let streamingAPI: URL public let streamingAPI: URL
@ -25,8 +27,8 @@ public extension Identity {
} }
struct Account: Codable, Hashable { struct Account: Codable, Hashable {
public let id: String public let id: Mastodon.Account.Id
public let identityID: UUID public let identityId: Identity.Id
public let username: String public let username: String
public let displayName: String public let displayName: String
public let url: URL public let url: URL

View File

@ -4,12 +4,12 @@ import Foundation
import Mastodon import Mastodon
public struct IdentityFixture { public struct IdentityFixture {
public let id: UUID public let id: Identity.Id
public let instanceURL: URL public let instanceURL: URL
public let instance: Instance? public let instance: Instance?
public let account: Account? 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.id = id
self.instanceURL = instanceURL self.instanceURL = instanceURL
self.instance = instance self.instance = instance

View File

@ -5,8 +5,8 @@ import Mastodon
public struct LoadMore: Hashable { public struct LoadMore: Hashable {
public let timeline: Timeline public let timeline: Timeline
public let afterStatusId: String public let afterStatusId: Status.Id
public let beforeStatusId: String public let beforeStatusId: Status.Id
} }
public extension LoadMore { public extension LoadMore {

View File

@ -9,10 +9,12 @@ public enum Timeline: Hashable {
case federated case federated
case list(List) case list(List)
case tag(String) case tag(String)
case profile(accountId: String, profileCollection: ProfileCollection) case profile(accountId: Account.Id, profileCollection: ProfileCollection)
} }
public extension Timeline { public extension Timeline {
typealias Id = String
static let unauthenticatedDefaults: [Timeline] = [.local, .federated] static let unauthenticatedDefaults: [Timeline] = [.local, .federated]
static let authenticatedDefaults: [Timeline] = [.home, .local, .federated] static let authenticatedDefaults: [Timeline] = [.home, .local, .federated]
@ -29,7 +31,7 @@ public extension Timeline {
} }
extension Timeline: Identifiable { extension Timeline: Identifiable {
public var id: String { public var id: Id {
switch self { switch self {
case .home: case .home:
return "home" return "home"

View File

@ -28,7 +28,7 @@ extension IdentityDatabase {
try db.create(table: "account", ifNotExists: true) { t in try db.create(table: "account", ifNotExists: true) { t in
t.column("id", .text).primaryKey(onConflict: .replace) t.column("id", .text).primaryKey(onConflict: .replace)
t.column("identityID", .text).notNull() t.column("identityId", .text).notNull()
.references("identityRecord", onDelete: .cascade) .references("identityRecord", onDelete: .cascade)
t.column("username", .text).notNull() t.column("username", .text).notNull()
t.column("displayName", .text).notNull() t.column("displayName", .text).notNull()

View File

@ -22,7 +22,7 @@ public struct IdentityDatabase {
var configuration = Configuration() var configuration = Configuration()
configuration.prepareDatabase { 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) databaseWriter = try DatabasePool(path: path, configuration: configuration)
@ -33,7 +33,7 @@ public struct IdentityDatabase {
} }
public extension 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( databaseWriter.writePublisher(
updates: IdentityRecord( updates: IdentityRecord(
id: id, id: id,
@ -50,23 +50,23 @@ public extension IdentityDatabase {
.eraseToAnyPublisher() .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) databaseWriter.writePublisher(updates: IdentityRecord.filter(IdentityRecord.Columns.id == id).deleteAll)
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updateLastUsedAt(identityID: UUID) -> AnyPublisher<Never, Error> { func updateLastUsedAt(id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == identityID) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.lastUsedAt.set(to: Date())) .updateAll($0, IdentityRecord.Columns.lastUsedAt.set(to: Date()))
} }
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updateInstance(_ instance: Instance, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { func updateInstance(_ instance: Instance, id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
try Identity.Instance( try Identity.Instance(
uri: instance.uri, uri: instance.uri,
@ -75,18 +75,18 @@ public extension IdentityDatabase {
thumbnail: instance.thumbnail) thumbnail: instance.thumbnail)
.save($0) .save($0)
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == identityID) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.instanceURI.set(to: instance.uri)) .updateAll($0, IdentityRecord.Columns.instanceURI.set(to: instance.uri))
} }
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updateAccount(_ account: Account, forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { func updateAccount(_ account: Account, id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher( databaseWriter.writePublisher(
updates: Identity.Account( updates: Identity.Account(
id: account.id, id: account.id,
identityID: identityID, identityId: id,
username: account.username, username: account.username,
displayName: account.displayName, displayName: account.displayName,
url: account.url, url: account.url,
@ -100,7 +100,7 @@ public extension IdentityDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func confirmIdentity(id: UUID) -> AnyPublisher<Never, Error> { func confirmIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == id) .filter(IdentityRecord.Columns.id == id)
@ -110,43 +110,41 @@ public extension IdentityDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updatePreferences(_ preferences: Mastodon.Preferences, func updatePreferences(_ preferences: Mastodon.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == identityID) guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == id)
.fetchOne($0)? .fetchOne($0)?
.preferences else { .preferences else {
throw IdentityDatabaseError.identityNotFound throw IdentityDatabaseError.identityNotFound
} }
try Self.writePreferences(storedPreferences.updated(from: preferences), id: identityID)($0) try Self.writePreferences(storedPreferences.updated(from: preferences), id: id)($0)
} }
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updatePreferences(_ preferences: Identity.Preferences, func updatePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: id))
databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: identityID))
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updatePushSubscription(alerts: PushSubscription.Alerts, func updatePushSubscription(alerts: PushSubscription.Alerts,
deviceToken: Data? = nil, deviceToken: Data? = nil,
forIdentityID identityID: UUID) -> AnyPublisher<Never, Error> { id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
let data = try IdentityRecord.databaseJSONEncoder( let data = try IdentityRecord.databaseJSONEncoder(
for: IdentityRecord.Columns.pushSubscriptionAlerts.name) for: IdentityRecord.Columns.pushSubscriptionAlerts.name)
.encode(alerts) .encode(alerts)
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == identityID) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.pushSubscriptionAlerts.set(to: data)) .updateAll($0, IdentityRecord.Columns.pushSubscriptionAlerts.set(to: data))
if let deviceToken = deviceToken { if let deviceToken = deviceToken {
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == identityID) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.lastRegisteredDeviceToken.set(to: deviceToken)) .updateAll($0, IdentityRecord.Columns.lastRegisteredDeviceToken.set(to: deviceToken))
} }
} }
@ -154,7 +152,7 @@ public extension IdentityDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func identityObservation(id: UUID, immediate: Bool) -> AnyPublisher<Identity, Error> { func identityObservation(id: Identity.Id, immediate: Bool) -> AnyPublisher<Identity, Error> {
ValueObservation.tracking( ValueObservation.tracking(
IdentityInfo.request(IdentityRecord.filter(IdentityRecord.Columns.id == id)).fetchOne) IdentityInfo.request(IdentityRecord.filter(IdentityRecord.Columns.id == id)).fetchOne)
.removeDuplicates() .removeDuplicates()
@ -176,7 +174,7 @@ public extension IdentityDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func recentIdentitiesObservation(excluding: UUID) -> AnyPublisher<[Identity], Error> { func recentIdentitiesObservation(excluding: Identity.Id) -> AnyPublisher<[Identity], Error> {
ValueObservation.tracking( ValueObservation.tracking(
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc)) IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
.filter(IdentityRecord.Columns.id != excluding) .filter(IdentityRecord.Columns.id != excluding)
@ -188,7 +186,7 @@ public extension IdentityDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> { func immediateMostRecentlyUsedIdentityIdObservation() -> AnyPublisher<Identity.Id?, Error> {
ValueObservation.tracking( ValueObservation.tracking(
IdentityRecord.select(IdentityRecord.Columns.id) IdentityRecord.select(IdentityRecord.Columns.id)
.order(IdentityRecord.Columns.lastUsedAt.desc).fetchOne) .order(IdentityRecord.Columns.lastUsedAt.desc).fetchOne)
@ -210,7 +208,7 @@ public extension IdentityDatabase {
private extension IdentityDatabase { private extension IdentityDatabase {
static let name = "identity" 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( let data = try IdentityRecord.databaseJSONEncoder(
for: IdentityRecord.Columns.preferences.name).encode(preferences) for: IdentityRecord.Columns.preferences.name).encode(preferences)

View File

@ -5,7 +5,7 @@ import GRDB
import Mastodon import Mastodon
struct IdentityRecord: Codable, Hashable, FetchableRecord, PersistableRecord { struct IdentityRecord: Codable, Hashable, FetchableRecord, PersistableRecord {
let id: UUID let id: Identity.Id
let url: URL let url: URL
let authenticated: Bool let authenticated: Bool
let pending: Bool let pending: Bool

View File

@ -2,7 +2,7 @@
import Foundation import Foundation
public typealias HTTPStub = Result<(URLResponse, Data), Error> public typealias HTTPStub = Result<(HTTPURLResponse, Data), Error>
public protocol Stubbing { public protocol Stubbing {
func stub(url: URL) -> HTTPStub? func stub(url: URL) -> HTTPStub?

View File

@ -9,7 +9,7 @@ public final class Account: Codable, Identifiable {
public let verifiedAt: Date? public let verifiedAt: Date?
} }
public let id: String public let id: Id
public let username: String public let username: String
public let acct: String public let acct: String
public let displayName: String public let displayName: String
@ -30,7 +30,7 @@ public final class Account: Codable, Identifiable {
@DecodableDefault.False public private(set) var discoverable: Bool @DecodableDefault.False public private(set) var discoverable: Bool
public var moved: Account? public var moved: Account?
public init(id: String, public init(id: Id,
username: String, username: String,
acct: String, acct: String,
displayName: String, displayName: String,
@ -73,6 +73,10 @@ public final class Account: Codable, Identifiable {
} }
} }
public extension Account {
typealias Id = String
}
extension Account: Hashable { extension Account: Hashable {
public static func == (lhs: Account, rhs: Account) -> Bool { public static func == (lhs: Account, rhs: Account) -> Bool {
return lhs.id == rhs.id && return lhs.id == rhs.id &&

View File

@ -3,7 +3,7 @@
import Foundation import Foundation
public struct AppAuthorization: Codable { public struct AppAuthorization: Codable {
public let id: String public let id: Id
public let clientId: String public let clientId: String
public let clientSecret: String public let clientSecret: String
public let name: String public let name: String
@ -11,3 +11,7 @@ public struct AppAuthorization: Codable {
public let website: String? public let website: String?
public let vapidKey: String? public let vapidKey: String?
} }
public extension AppAuthorization {
typealias Id = String
}

View File

@ -32,7 +32,7 @@ public struct Attachment: Codable, Hashable {
} }
// swiftlint:enable nesting // swiftlint:enable nesting
public let id: String public let id: Id
public let type: AttachmentType public let type: AttachmentType
public let url: URL public let url: URL
public let remoteUrl: URL? public let remoteUrl: URL?
@ -41,3 +41,7 @@ public struct Attachment: Codable, Hashable {
public let meta: Meta? public let meta: Meta?
public let description: String? public let description: String?
} }
public extension Attachment {
typealias Id = String
}

View File

@ -14,7 +14,7 @@ public struct Filter: Codable, Hashable, Identifiable {
public static var unknownCase: Self { .unknown } public static var unknownCase: Self { .unknown }
} }
public let id: String public let id: Id
public var phrase: String public var phrase: String
public var context: [Context] public var context: [Context]
public var expiresAt: Date? public var expiresAt: Date?
@ -23,8 +23,10 @@ public struct Filter: Codable, Hashable, Identifiable {
} }
public extension Filter { public extension Filter {
static let newFilterID: String = "com.metabolist.metatext.new-filter-id" typealias Id = String
static let new = Self(id: newFilterID,
static let newFilterId: Id = "com.metabolist.metatext.new-filter-id"
static let new = Self(id: newFilterId,
phrase: "", phrase: "",
context: [], context: [],
expiresAt: nil, expiresAt: nil,

View File

@ -3,11 +3,15 @@
import Foundation import Foundation
public struct List: Codable, Hashable, Identifiable { public struct List: Codable, Hashable, Identifiable {
public let id: String public let id: Id
public let title: String public let title: String
public init(id: String, title: String) { public init(id: Id, title: String) {
self.id = id self.id = id
self.title = title self.title = title
} }
} }
public extension List {
typealias Id = String
}

View File

@ -6,5 +6,5 @@ public struct Mention: Codable, Hashable {
public let url: URL public let url: URL
public let username: String public let username: String
public let acct: String public let acct: String
public let id: String public let id: Account.Id
} }

View File

@ -8,7 +8,7 @@ public struct Poll: Codable, Hashable {
public var votesCount: Int public var votesCount: Int
} }
public let id: String public let id: Id
public let expiresAt: Date public let expiresAt: Date
public let expired: Bool public let expired: Bool
public let multiple: Bool public let multiple: Bool
@ -19,3 +19,7 @@ public struct Poll: Codable, Hashable {
public let options: [Option] public let options: [Option]
public let emojis: [Emoji] public let emojis: [Emoji]
} }
public extension Poll {
typealias Id = String
}

View File

@ -13,7 +13,7 @@ public final class Status: Codable, Identifiable {
public static var unknownCase: Self { .unknown } public static var unknownCase: Self { .unknown }
} }
public let id: String public let id: Status.Id
public let uri: String public let uri: String
public let createdAt: Date public let createdAt: Date
public let account: Account public let account: Account
@ -30,8 +30,8 @@ public final class Status: Codable, Identifiable {
@DecodableDefault.Zero public private(set) var repliesCount: Int @DecodableDefault.Zero public private(set) var repliesCount: Int
public let application: Application? public let application: Application?
public let url: URL? public let url: URL?
public let inReplyToId: String? public let inReplyToId: Status.Id?
public let inReplyToAccountId: String? public let inReplyToAccountId: Account.Id?
public let reblog: Status? public let reblog: Status?
public let poll: Poll? public let poll: Poll?
public let card: Card? public let card: Card?
@ -44,7 +44,7 @@ public final class Status: Codable, Identifiable {
public let pinned: Bool? public let pinned: Bool?
public init( public init(
id: String, id: Status.Id,
uri: String, uri: String,
createdAt: Date, createdAt: Date,
account: Account, account: Account,
@ -61,8 +61,8 @@ public final class Status: Codable, Identifiable {
repliesCount: Int, repliesCount: Int,
application: Application?, application: Application?,
url: URL?, url: URL?,
inReplyToId: String?, inReplyToId: Status.Id?,
inReplyToAccountId: String?, inReplyToAccountId: Account.Id?,
reblog: Status?, reblog: Status?,
poll: Poll?, poll: Poll?,
card: Card?, card: Card?,
@ -106,6 +106,8 @@ public final class Status: Codable, Identifiable {
} }
public extension Status { public extension Status {
typealias Id = String
var displayStatus: Status { var displayStatus: Status {
reblog ?? self reblog ?? self
} }

View File

@ -6,7 +6,7 @@ import Mastodon
public enum AccessTokenEndpoint { public enum AccessTokenEndpoint {
case oauthToken( case oauthToken(
clientID: String, clientId: String,
clientSecret: String, clientSecret: String,
grantType: String, grantType: String,
scopes: String, scopes: String,
@ -58,9 +58,9 @@ extension AccessTokenEndpoint: Endpoint {
public var jsonBody: [String: Any]? { public var jsonBody: [String: Any]? {
switch self { switch self {
case let .oauthToken(clientID, clientSecret, grantType, scopes, code, redirectURI): case let .oauthToken(clientId, clientSecret, grantType, scopes, code, redirectURI):
var params = [ var params = [
"client_id": clientID, "client_id": clientId,
"client_secret": clientSecret, "client_secret": clientSecret,
"grant_type": grantType, "grant_type": grantType,
"scope": scopes] "scope": scopes]

View File

@ -6,7 +6,7 @@ import Mastodon
public enum AccountEndpoint { public enum AccountEndpoint {
case verifyCredentials case verifyCredentials
case accounts(id: String) case accounts(id: Account.Id)
} }
extension AccountEndpoint: Endpoint { extension AccountEndpoint: Endpoint {

View File

@ -5,8 +5,8 @@ import HTTP
import Mastodon import Mastodon
public enum AccountsEndpoint { public enum AccountsEndpoint {
case statusRebloggedBy(id: String) case rebloggedBy(id: Status.Id)
case statusFavouritedBy(id: String) case favouritedBy(id: Status.Id)
} }
extension AccountsEndpoint: Endpoint { extension AccountsEndpoint: Endpoint {
@ -14,23 +14,23 @@ extension AccountsEndpoint: Endpoint {
public var context: [String] { public var context: [String] {
switch self { switch self {
case .statusRebloggedBy, .statusFavouritedBy: case .rebloggedBy, .favouritedBy:
return defaultContext + ["statuses"] return defaultContext + ["statuses"]
} }
} }
public var pathComponentsInContext: [String] { public var pathComponentsInContext: [String] {
switch self { switch self {
case let .statusRebloggedBy(id): case let .rebloggedBy(id):
return [id, "reblogged_by"] return [id, "reblogged_by"]
case let .statusFavouritedBy(id): case let .favouritedBy(id):
return [id, "favourited_by"] return [id, "favourited_by"]
} }
} }
public var method: HTTPMethod { public var method: HTTPMethod {
switch self { switch self {
case .statusRebloggedBy, .statusFavouritedBy: case .rebloggedBy, .favouritedBy:
return .get return .get
} }
} }

View File

@ -5,7 +5,7 @@ import HTTP
import Mastodon import Mastodon
public enum ContextEndpoint { public enum ContextEndpoint {
case context(id: String) case context(id: Status.Id)
} }
extension ContextEndpoint: Endpoint { extension ContextEndpoint: Endpoint {

View File

@ -5,9 +5,9 @@ import HTTP
import Mastodon import Mastodon
public enum DeletionEndpoint { public enum DeletionEndpoint {
case oauthRevoke(token: String, clientID: String, clientSecret: String) case oauthRevoke(token: String, clientId: String, clientSecret: String)
case list(id: String) case list(id: List.Id)
case filter(id: String) case filter(id: Filter.Id)
} }
extension DeletionEndpoint: Endpoint { extension DeletionEndpoint: Endpoint {
@ -44,8 +44,8 @@ extension DeletionEndpoint: Endpoint {
public var jsonBody: [String: Any]? { public var jsonBody: [String: Any]? {
switch self { switch self {
case let .oauthRevoke(token, clientID, clientSecret): case let .oauthRevoke(token, clientId, clientSecret):
return ["token": token, "client_id": clientID, "client_secret": clientSecret] return ["token": token, "client_id": clientId, "client_secret": clientSecret]
case .list, .filter: case .list, .filter:
return nil return nil
} }

View File

@ -12,7 +12,7 @@ public enum FilterEndpoint {
wholeWord: Bool, wholeWord: Bool,
expiresIn: Date?) expiresIn: Date?)
case update( case update(
id: String, id: Filter.Id,
phrase: String, phrase: String,
context: [Filter.Context], context: [Filter.Context],
irreversible: Bool, irreversible: Bool,

View File

@ -6,16 +6,16 @@ import Mastodon
public struct Paged<T: Endpoint> { public struct Paged<T: Endpoint> {
public let endpoint: T public let endpoint: T
public let maxID: String? public let maxId: String?
public let minID: String? public let minId: String?
public let sinceID: String? public let sinceId: String?
public let limit: Int? 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.endpoint = endpoint
self.maxID = maxID self.maxId = maxId
self.minID = minID self.minId = minId
self.sinceID = sinceID self.sinceId = sinceId
self.limit = limit self.limit = limit
} }
} }
@ -34,9 +34,9 @@ extension Paged: Endpoint {
public var queryParameters: [String: String]? { public var queryParameters: [String: String]? {
var queryParameters = endpoint.queryParameters ?? [String: String]() var queryParameters = endpoint.queryParameters ?? [String: String]()
queryParameters["max_id"] = maxID queryParameters["max_id"] = maxId
queryParameters["min_id"] = minID queryParameters["min_id"] = minId
queryParameters["since_id"] = sinceID queryParameters["since_id"] = sinceId
if let limit = limit { if let limit = limit {
queryParameters["limit"] = String(limit) queryParameters["limit"] = String(limit)
@ -50,9 +50,9 @@ extension Paged: Endpoint {
public struct PagedResult<T: Decodable>: Decodable { public struct PagedResult<T: Decodable>: Decodable {
public struct Info: Decodable { public struct Info: Decodable {
public let maxID: String? public let maxId: String?
public let minID: String? public let minId: String?
public let sinceID: String? public let sinceId: String?
} }
public let result: T public let result: T

View File

@ -5,9 +5,9 @@ import HTTP
import Mastodon import Mastodon
public enum StatusEndpoint { public enum StatusEndpoint {
case status(id: String) case status(id: Status.Id)
case favourite(id: String) case favourite(id: Status.Id)
case unfavourite(id: String) case unfavourite(id: Status.Id)
} }
extension StatusEndpoint: Endpoint { extension StatusEndpoint: Endpoint {

View File

@ -8,8 +8,8 @@ public enum StatusesEndpoint {
case timelinesPublic(local: Bool) case timelinesPublic(local: Bool)
case timelinesTag(String) case timelinesTag(String)
case timelinesHome case timelinesHome
case timelinesList(id: String) case timelinesList(id: List.Id)
case accountsStatuses(id: String, excludeReplies: Bool, onlyMedia: Bool, pinned: Bool) case accountsStatuses(id: Account.Id, excludeReplies: Bool, onlyMedia: Bool, pinned: Bool)
} }
extension StatusesEndpoint: Endpoint { extension StatusesEndpoint: Endpoint {

View File

@ -39,17 +39,17 @@ extension MastodonAPIClient {
public func pagedRequest<E: Endpoint>( public func pagedRequest<E: Endpoint>(
_ endpoint: E, _ endpoint: E,
maxID: String? = nil, maxId: String? = nil,
minID: String? = nil, minId: String? = nil,
sinceID: String? = nil, sinceId: String? = nil,
limit: Int? = nil) -> AnyPublisher<PagedResult<E.ResultType>, Error> { 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 dataTask = dataTaskPublisher(pagedTarget).share()
let decoded = dataTask.map(\.data).decode(type: E.ResultType.self, decoder: decoder) let decoded = dataTask.map(\.data).decode(type: E.ResultType.self, decoder: decoder)
let info = dataTask.map { _, response -> PagedResult<E.ResultType>.Info in let info = dataTask.map { _, response -> PagedResult<E.ResultType>.Info in
var maxID: String? var maxId: String?
var minID: String? var minId: String?
var sinceID: String? var sinceId: String?
if let links = response.value(forHTTPHeaderField: "Link") { if let links = response.value(forHTTPHeaderField: "Link") {
let queryItems = Self.linkDataDetector.matches( let queryItems = Self.linkDataDetector.matches(
@ -62,12 +62,12 @@ extension MastodonAPIClient {
} }
.reduce([], +) .reduce([], +)
maxID = queryItems.first { $0.name == "max_id" }?.value maxId = queryItems.first { $0.name == "max_id" }?.value
minID = queryItems.first { $0.name == "min_id" }?.value minId = queryItems.first { $0.name == "min_id" }?.value
sinceID = queryItems.first { $0.name == "since_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() return decoded.zip(info).map(PagedResult.init(result:info:)).eraseToAnyPublisher()

View File

@ -62,7 +62,7 @@ enum NotificationServiceError: Error {
} }
private extension NotificationService { private extension NotificationService {
static let identityIDUserInfoKey = "i" static let identityIdUserInfoKey = "i"
static let encryptedMessageUserInfoKey = "m" static let encryptedMessageUserInfoKey = "m"
static let saltUserInfoKey = "s" static let saltUserInfoKey = "s"
static let serverPublicKeyUserInfoKey = "k" static let serverPublicKeyUserInfoKey = "k"
@ -82,8 +82,8 @@ private extension NotificationService {
static func extractAndDecrypt(userInfo: [AnyHashable: Any]) throws -> Data { static func extractAndDecrypt(userInfo: [AnyHashable: Any]) throws -> Data {
guard guard
let identityIDString = userInfo[identityIDUserInfoKey] as? String, let identityIdString = userInfo[identityIdUserInfoKey] as? String,
let identityID = UUID(uuidString: identityIDString), let identityId = UUID(uuidString: identityIdString),
let encryptedMessageBase64 = (userInfo[encryptedMessageUserInfoKey] as? String)?.URLSafeBase64ToBase64(), let encryptedMessageBase64 = (userInfo[encryptedMessageUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
let encryptedMessage = Data(base64Encoded: encryptedMessageBase64), let encryptedMessage = Data(base64Encoded: encryptedMessageBase64),
let saltBase64 = (userInfo[saltUserInfoKey] as? String)?.URLSafeBase64ToBase64(), let saltBase64 = (userInfo[saltUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
@ -92,7 +92,7 @@ private extension NotificationService {
let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64) let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64)
else { throw NotificationServiceError.userInfoDataAbsent } else { throw NotificationServiceError.userInfoDataAbsent }
let secretsService = Secrets(identityID: identityID, keychain: LiveKeychain.self) let secretsService = Secrets(identityId: identityId, keychain: LiveKeychain.self)
guard guard
let auth = try secretsService.getPushAuth(), let auth = try secretsService.getPushAuth(),

View File

@ -14,11 +14,11 @@ enum SecretsStorableError: Error {
} }
public struct Secrets { public struct Secrets {
public let identityID: UUID public let identityId: UUID
private let keychain: Keychain.Type private let keychain: Keychain.Type
public init(identityID: UUID, keychain: Keychain.Type) { public init(identityId: UUID, keychain: Keychain.Type) {
self.identityID = identityID self.identityId = identityId
self.keychain = keychain self.keychain = keychain
} }
} }
@ -26,7 +26,7 @@ public struct Secrets {
public extension Secrets { public extension Secrets {
enum Item: String, CaseIterable { enum Item: String, CaseIterable {
case instanceURL case instanceURL
case clientID case clientId
case clientSecret case clientSecret
case accessToken case accessToken
case pushKey case pushKey
@ -56,12 +56,12 @@ extension Secrets.Item {
public extension Secrets { public extension Secrets {
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#key // 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 passphraseData: Data
let scopedSecrets: Secrets? let scopedSecrets: Secrets?
if let identityID = identityID { if let identityId = identityId {
scopedSecrets = Secrets(identityID: identityID, keychain: keychain) scopedSecrets = Secrets(identityId: identityId, keychain: keychain)
} else { } else {
scopedSecrets = nil scopedSecrets = nil
} }
@ -114,12 +114,12 @@ public extension Secrets {
try set(instanceURL, forItem: .instanceURL) try set(instanceURL, forItem: .instanceURL)
} }
func getClientID() throws -> String { func getClientId() throws -> String {
try item(.clientID) try item(.clientId)
} }
func setClientID(_ clientID: String) throws { func setClientId(_ clientId: String) throws {
try set(clientID, forItem: .clientID) try set(clientId, forItem: .clientId)
} }
func getClientSecret() throws -> String { func getClientSecret() throws -> String {
@ -200,7 +200,7 @@ private extension Secrets {
} }
func scopedKey(item: Item) -> String { func scopedKey(item: Item) -> String {
identityID.uuidString + "." + item.rawValue identityId.uuidString + "." + item.rawValue
} }
func set(_ data: SecretsStorable, forItem item: Item) throws { func set(_ data: SecretsStorable, forItem item: Item) throws {

View File

@ -8,14 +8,14 @@ import MastodonAPI
public struct AccountListService { public struct AccountListService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[[CollectionItem]], Error>
public let nextPageMaxIDs: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let navigationService: NavigationService public let navigationService: NavigationService
private let list: AccountList private let list: AccountList
private let endpoint: AccountsEndpoint private let endpoint: AccountsEndpoint
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
private let nextPageMaxIDsSubject = PassthroughSubject<String, Never>() private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
init(endpoint: AccountsEndpoint, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { init(endpoint: AccountsEndpoint, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
list = AccountList() list = AccountList()
@ -25,18 +25,18 @@ public struct AccountListService {
sections = contentDatabase.accountListObservation(list) sections = contentDatabase.accountListObservation(list)
.map { [$0.map(CollectionItem.account)] } .map { [$0.map(CollectionItem.account)] }
.eraseToAnyPublisher() .eraseToAnyPublisher()
nextPageMaxIDs = nextPageMaxIDsSubject.eraseToAnyPublisher() nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
} }
} }
extension AccountListService: CollectionService { extension AccountListService: CollectionService {
public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> { public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
mastodonAPIClient.pagedRequest(endpoint, maxID: maxID, minID: minID) mastodonAPIClient.pagedRequest(endpoint, maxId: maxId, minId: minId)
.handleEvents(receiveOutput: { .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) } .flatMap { contentDatabase.append(accounts: $0.result, toList: list) }
.eraseToAnyPublisher() .eraseToAnyPublisher()

View File

@ -8,11 +8,11 @@ import MastodonAPI
import Secrets import Secrets
public struct AllIdentitiesService { public struct AllIdentitiesService {
public let identitiesCreated: AnyPublisher<UUID, Never> public let identitiesCreated: AnyPublisher<Identity.Id, Never>
private let environment: AppEnvironment private let environment: AppEnvironment
private let database: IdentityDatabase private let database: IdentityDatabase
private let identitiesCreatedSubject = PassthroughSubject<UUID, Never>() private let identitiesCreatedSubject = PassthroughSubject<Identity.Id, Never>()
public init(environment: AppEnvironment) throws { public init(environment: AppEnvironment) throws {
self.environment = environment self.environment = environment
@ -30,17 +30,17 @@ public extension AllIdentitiesService {
case browsing case browsing
} }
func identityService(id: UUID) throws -> IdentityService { func identityService(id: Identity.Id) throws -> IdentityService {
try IdentityService(id: id, database: database, environment: environment) try IdentityService(id: id, database: database, environment: environment)
} }
func immediateMostRecentlyUsedIdentityIDObservation() -> AnyPublisher<UUID?, Error> { func immediateMostRecentlyUsedIdentityIdObservation() -> AnyPublisher<Identity.Id?, Error> {
database.immediateMostRecentlyUsedIdentityIDObservation() database.immediateMostRecentlyUsedIdentityIdObservation()
} }
func createIdentity(url: URL, kind: IdentityCreation) -> AnyPublisher<Never, Error> { func createIdentity(url: URL, kind: IdentityCreation) -> AnyPublisher<Never, Error> {
let id = environment.uuid() let id = environment.uuid()
let secrets = Secrets(identityID: id, keychain: environment.keychain) let secrets = Secrets(identityId: id, keychain: environment.keychain)
do { do {
try secrets.setInstanceURL(url) try secrets.setInstanceURL(url)
@ -76,7 +76,7 @@ public extension AllIdentitiesService {
return authenticationPublisher return authenticationPublisher
.tryMap { .tryMap {
try secrets.setClientID($0.clientId) try secrets.setClientId($0.clientId)
try secrets.setClientSecret($0.clientSecret) try secrets.setClientSecret($0.clientSecret)
try secrets.setAccessToken($1.accessToken) try secrets.setAccessToken($1.accessToken)
} }
@ -84,13 +84,13 @@ public extension AllIdentitiesService {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func deleteIdentity(id: UUID) -> AnyPublisher<Never, Error> { func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
database.deleteIdentity(id: id) database.deleteIdentity(id: id)
.collect() .collect()
.tryMap { _ -> AnyPublisher<Never, Error> in .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() } defer { secrets.deleteAllItems() }
@ -100,7 +100,7 @@ public extension AllIdentitiesService {
instanceURL: try secrets.getInstanceURL()) instanceURL: try secrets.getInstanceURL())
.request(DeletionEndpoint.oauthRevoke( .request(DeletionEndpoint.oauthRevoke(
token: try secrets.getAccessToken(), token: try secrets.getAccessToken(),
clientID: try secrets.getClientID(), clientId: try secrets.getClientId(),
clientSecret: try secrets.getClientSecret())) clientSecret: try secrets.getClientSecret()))
.ignoreOutput() .ignoreOutput()
.eraseToAnyPublisher() .eraseToAnyPublisher()

View File

@ -29,7 +29,8 @@ extension AuthenticationService {
.eraseToAnyPublisher() .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 redirectURI = OAuth.registrationCallbackURL.appendingPathComponent(id.uuidString)
let authorization = appAuthorization(redirectURI: redirectURI) let authorization = appAuthorization(redirectURI: redirectURI)
.share() .share()
@ -38,7 +39,7 @@ extension AuthenticationService {
authorization.flatMap { appAuthorization -> AnyPublisher<AccessToken, Error> in authorization.flatMap { appAuthorization -> AnyPublisher<AccessToken, Error> in
mastodonAPIClient.request( mastodonAPIClient.request(
AccessTokenEndpoint.oauthToken( AccessTokenEndpoint.oauthToken(
clientID: appAuthorization.clientId, clientId: appAuthorization.clientId,
clientSecret: appAuthorization.clientSecret, clientSecret: appAuthorization.clientSecret,
grantType: OAuth.registrationGrantType, grantType: OAuth.registrationGrantType,
scopes: OAuth.scopes, scopes: OAuth.scopes,
@ -134,7 +135,7 @@ private extension AuthenticationService {
.flatMap { .flatMap {
mastodonAPIClient.request( mastodonAPIClient.request(
AccessTokenEndpoint.oauthToken( AccessTokenEndpoint.oauthToken(
clientID: appAuthorization.clientId, clientId: appAuthorization.clientId,
clientSecret: appAuthorization.clientSecret, clientSecret: appAuthorization.clientSecret,
grantType: OAuth.authorizationCodeGrantType, grantType: OAuth.authorizationCodeGrantType,
scopes: OAuth.scopes, scopes: OAuth.scopes,

View File

@ -4,17 +4,17 @@ import Combine
public protocol CollectionService { public protocol CollectionService {
var sections: AnyPublisher<[[CollectionItem]], Error> { get } 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 title: AnyPublisher<String, Never> { get }
var navigationService: NavigationService { get } var navigationService: NavigationService { get }
var contextParentID: String? { get } var contextParentId: String? { get }
func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error>
} }
extension CollectionService { 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 title: AnyPublisher<String, Never> { Empty().eraseToAnyPublisher() }
public var contextParentID: String? { nil } public var contextParentId: String? { nil }
} }

View File

@ -9,27 +9,27 @@ import MastodonAPI
public struct ContextService { public struct ContextService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[[CollectionItem]], Error>
public let navigationService: NavigationService 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 mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
init(parentID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { init(id: Status.Id, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.parentID = parentID self.id = id
self.mastodonAPIClient = mastodonAPIClient self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase self.contentDatabase = contentDatabase
sections = contentDatabase.contextObservation(parentID: parentID) sections = contentDatabase.contextObservation(id: id)
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
} }
} }
extension ContextService: CollectionService { extension ContextService: CollectionService {
public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> { public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(StatusEndpoint.status(id: parentID)) mastodonAPIClient.request(StatusEndpoint.status(id: id))
.flatMap(contentDatabase.insert(status:)) .flatMap(contentDatabase.insert(status:))
.merge(with: mastodonAPIClient.request(ContextEndpoint.context(id: parentID)) .merge(with: mastodonAPIClient.request(ContextEndpoint.context(id: id))
.flatMap { contentDatabase.insert(context: $0, parentID: parentID) }) .flatMap { contentDatabase.insert(context: $0, parentId: id) })
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }

View File

@ -9,25 +9,25 @@ import MastodonAPI
import Secrets import Secrets
public struct IdentityService { public struct IdentityService {
private let identityID: UUID private let id: Identity.Id
private let identityDatabase: IdentityDatabase private let identityDatabase: IdentityDatabase
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
private let environment: AppEnvironment private let environment: AppEnvironment
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
private let secrets: Secrets private let secrets: Secrets
init(id: UUID, database: IdentityDatabase, environment: AppEnvironment) throws { init(id: Identity.Id, database: IdentityDatabase, environment: AppEnvironment) throws {
identityID = id self.id = id
identityDatabase = database identityDatabase = database
self.environment = environment self.environment = environment
secrets = Secrets( secrets = Secrets(
identityID: id, identityId: id,
keychain: environment.keychain) keychain: environment.keychain)
mastodonAPIClient = MastodonAPIClient(session: environment.session, mastodonAPIClient = MastodonAPIClient(session: environment.session,
instanceURL: try secrets.getInstanceURL()) instanceURL: try secrets.getInstanceURL())
mastodonAPIClient.accessToken = try? secrets.getAccessToken() mastodonAPIClient.accessToken = try? secrets.getAccessToken()
contentDatabase = try ContentDatabase(identityID: id, contentDatabase = try ContentDatabase(id: id,
inMemory: environment.inMemoryContent, inMemory: environment.inMemoryContent,
keychain: environment.keychain) keychain: environment.keychain)
} }
@ -35,29 +35,29 @@ public struct IdentityService {
public extension IdentityService { public extension IdentityService {
func updateLastUse() -> AnyPublisher<Never, Error> { func updateLastUse() -> AnyPublisher<Never, Error> {
identityDatabase.updateLastUsedAt(identityID: identityID) identityDatabase.updateLastUsedAt(id: id)
} }
func verifyCredentials() -> AnyPublisher<Never, Error> { func verifyCredentials() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(AccountEndpoint.verifyCredentials) mastodonAPIClient.request(AccountEndpoint.verifyCredentials)
.flatMap { identityDatabase.updateAccount($0, forIdentityID: identityID) } .flatMap { identityDatabase.updateAccount($0, id: id) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func refreshServerPreferences() -> AnyPublisher<Never, Error> { func refreshServerPreferences() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(PreferencesEndpoint.preferences) mastodonAPIClient.request(PreferencesEndpoint.preferences)
.flatMap { identityDatabase.updatePreferences($0, forIdentityID: identityID) } .flatMap { identityDatabase.updatePreferences($0, id: id) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func refreshInstance() -> AnyPublisher<Never, Error> { func refreshInstance() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(InstanceEndpoint.instance) mastodonAPIClient.request(InstanceEndpoint.instance)
.flatMap { identityDatabase.updateInstance($0, forIdentityID: identityID) } .flatMap { identityDatabase.updateInstance($0, id: id) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func confirmIdentity() -> AnyPublisher<Never, Error> { func confirmIdentity() -> AnyPublisher<Never, Error> {
identityDatabase.confirmIdentity(id: identityID) identityDatabase.confirmIdentity(id: id)
} }
func identitiesObservation() -> AnyPublisher<[Identity], Error> { func identitiesObservation() -> AnyPublisher<[Identity], Error> {
@ -65,7 +65,7 @@ public extension IdentityService {
} }
func recentIdentitiesObservation() -> AnyPublisher<[Identity], Error> { func recentIdentitiesObservation() -> AnyPublisher<[Identity], Error> {
identityDatabase.recentIdentitiesObservation(excluding: identityID) identityDatabase.recentIdentitiesObservation(excluding: id)
} }
func refreshLists() -> AnyPublisher<Never, Error> { func refreshLists() -> AnyPublisher<Never, Error> {
@ -80,7 +80,7 @@ public extension IdentityService {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func deleteList(id: String) -> AnyPublisher<Never, Error> { func deleteList(id: List.Id) -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(DeletionEndpoint.list(id: id)) mastodonAPIClient.request(DeletionEndpoint.list(id: id))
.map { _ in id } .map { _ in id }
.flatMap(contentDatabase.deleteList(id:)) .flatMap(contentDatabase.deleteList(id:))
@ -88,7 +88,7 @@ public extension IdentityService {
} }
func observation(immediate: Bool) -> AnyPublisher<Identity, Error> { func observation(immediate: Bool) -> AnyPublisher<Identity, Error> {
identityDatabase.identityObservation(id: identityID, immediate: immediate) identityDatabase.identityObservation(id: id, immediate: immediate)
} }
func listsObservation() -> AnyPublisher<[Timeline], Error> { func listsObservation() -> AnyPublisher<[Timeline], Error> {
@ -122,7 +122,7 @@ public extension IdentityService {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func deleteFilter(id: String) -> AnyPublisher<Never, Error> { func deleteFilter(id: Filter.Id) -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(DeletionEndpoint.filter(id: id)) mastodonAPIClient.request(DeletionEndpoint.filter(id: id))
.flatMap { _ in contentDatabase.deleteFilter(id: id) } .flatMap { _ in contentDatabase.deleteFilter(id: id) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
@ -137,7 +137,7 @@ public extension IdentityService {
} }
func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher<Never, Error> { func updatePreferences(_ preferences: Identity.Preferences) -> AnyPublisher<Never, Error> {
identityDatabase.updatePreferences(preferences, forIdentityID: identityID) identityDatabase.updatePreferences(preferences, id: id)
.collect() .collect()
.filter { _ in preferences.useServerPostingReadingPreferences } .filter { _ in preferences.useServerPostingReadingPreferences }
.flatMap { _ in refreshServerPreferences() } .flatMap { _ in refreshServerPreferences() }
@ -157,7 +157,7 @@ public extension IdentityService {
let endpoint = Self.pushSubscriptionEndpointURL let endpoint = Self.pushSubscriptionEndpointURL
.appendingPathComponent(deviceToken.base16EncodedString()) .appendingPathComponent(deviceToken.base16EncodedString())
.appendingPathComponent(identityID.uuidString) .appendingPathComponent(id.uuidString)
return mastodonAPIClient.request( return mastodonAPIClient.request(
PushSubscriptionEndpoint.create( PushSubscriptionEndpoint.create(
@ -165,15 +165,15 @@ public extension IdentityService {
publicKey: publicKey, publicKey: publicKey,
auth: auth, auth: auth,
alerts: alerts)) alerts: alerts))
.map { ($0.alerts, deviceToken, identityID) } .map { ($0.alerts, deviceToken, id) }
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:)) .flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:id:))
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func updatePushSubscription(alerts: PushSubscription.Alerts) -> AnyPublisher<Never, Error> { func updatePushSubscription(alerts: PushSubscription.Alerts) -> AnyPublisher<Never, Error> {
mastodonAPIClient.request(PushSubscriptionEndpoint.update(alerts: alerts)) mastodonAPIClient.request(PushSubscriptionEndpoint.update(alerts: alerts))
.map { ($0.alerts, nil, identityID) } .map { ($0.alerts, nil, id) }
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:)) .flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:id:))
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }

View File

@ -20,8 +20,8 @@ public extension LoadMoreService {
func request(direction: LoadMore.Direction) -> AnyPublisher<Never, Error> { func request(direction: LoadMore.Direction) -> AnyPublisher<Never, Error> {
mastodonAPIClient.pagedRequest( mastodonAPIClient.pagedRequest(
loadMore.timeline.endpoint, loadMore.timeline.endpoint,
maxID: direction == .down ? loadMore.afterStatusId : nil, maxId: direction == .down ? loadMore.afterStatusId : nil,
minID: direction == .up ? loadMore.beforeStatusId : nil) minId: direction == .up ? loadMore.beforeStatusId : nil)
.flatMap { .flatMap {
contentDatabase.insert( contentDatabase.insert(
statuses: $0.result, statuses: $0.result,

View File

@ -36,10 +36,10 @@ public extension NavigationService {
mastodonAPIClient: mastodonAPIClient, mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase))) contentDatabase: contentDatabase)))
.eraseToAnyPublisher() .eraseToAnyPublisher()
} else if let accountID = accountID(url: url) { } else if let accountId = accountId(url: url) {
return Just(.profile(profileService(id: accountID))).eraseToAnyPublisher() return Just(.profile(profileService(id: accountId))).eraseToAnyPublisher()
} else if mastodonAPIClient.instanceURL.host == url.host, let statusID = url.statusID { } else if mastodonAPIClient.instanceURL.host == url.host, let statusId = url.statusId {
return Just(.collection(contextService(id: statusID))).eraseToAnyPublisher() return Just(.collection(contextService(id: statusId))).eraseToAnyPublisher()
} }
if url.shouldWebfinger { if url.shouldWebfinger {
@ -49,11 +49,11 @@ public extension NavigationService {
} }
} }
func contextService(id: String) -> ContextService { func contextService(id: Status.Id) -> ContextService {
ContextService(parentID: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) 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) ProfileService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
} }
@ -86,12 +86,12 @@ private extension NavigationService {
return nil return nil
} }
func accountID(url: URL) -> String? { func accountId(url: URL) -> String? {
if let mentionID = status?.mentions.first(where: { $0.url.path.lowercased() == url.path.lowercased() })?.id { if let mentionId = status?.mentions.first(where: { $0.url.path.lowercased() == url.path.lowercased() })?.id {
return mentionID return mentionId
} else if } else if
mastodonAPIClient.instanceURL.host == url.host { mastodonAPIClient.instanceURL.host == url.host {
return url.accountID return url.accountId
} }
return nil return nil
@ -131,21 +131,21 @@ private extension URL {
|| (pathComponents.count == 3 && pathComponents[0...1] == ["/", "users"]) || (pathComponents.count == 3 && pathComponents[0...1] == ["/", "users"])
} }
var accountID: String? { var accountId: Account.Id? {
if let accountID = pathComponents.last, pathComponents == ["/", "web", "accounts", accountID] { if let accountId = pathComponents.last, pathComponents == ["/", "web", "accounts", accountId] {
return accountID return accountId
} }
return nil return nil
} }
var statusID: String? { var statusId: Status.Id? {
guard let statusID = pathComponents.last else { return nil } guard let statusId = pathComponents.last else { return nil }
if pathComponents.count == 3, pathComponents[1].starts(with: "@") { if pathComponents.count == 3, pathComponents[1].starts(with: "@") {
return statusID return statusId
} else if pathComponents == ["/", "web", "statuses", statusID] { } else if pathComponents == ["/", "web", "statuses", statusId] {
return statusID return statusId
} }
return nil return nil
@ -160,6 +160,6 @@ private extension URL {
} }
var shouldWebfinger: Bool { var shouldWebfinger: Bool {
isAccountURL || accountID != nil || statusID != nil || tag != nil isAccountURL || accountId != nil || statusId != nil || tag != nil
} }
} }

View File

@ -9,7 +9,7 @@ import MastodonAPI
public struct ProfileService { public struct ProfileService {
public let accountServicePublisher: AnyPublisher<AccountService, Error> public let accountServicePublisher: AnyPublisher<AccountService, Error>
private let accountID: String private let id: Account.Id
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
@ -21,20 +21,20 @@ public struct ProfileService {
contentDatabase: contentDatabase) 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) self.init(id: id, account: nil, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
} }
private init( private init(
id: String, id: Account.Id,
account: Account?, account: Account?,
mastodonAPIClient: MastodonAPIClient, mastodonAPIClient: MastodonAPIClient,
contentDatabase: ContentDatabase) { contentDatabase: ContentDatabase) {
accountID = id self.id = id
self.mastodonAPIClient = mastodonAPIClient self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase self.contentDatabase = contentDatabase
var accountPublisher = contentDatabase.accountObservation(id: accountID) var accountPublisher = contentDatabase.accountObservation(id: id)
if let account = account { if let account = account {
accountPublisher = accountPublisher accountPublisher = accountPublisher
@ -52,7 +52,7 @@ public struct ProfileService {
public extension ProfileService { public extension ProfileService {
func timelineService(profileCollection: ProfileCollection) -> TimelineService { func timelineService(profileCollection: ProfileCollection) -> TimelineService {
TimelineService( TimelineService(
timeline: .profile(accountId: accountID, profileCollection: profileCollection), timeline: .profile(accountId: id, profileCollection: profileCollection),
mastodonAPIClient: mastodonAPIClient, mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) contentDatabase: contentDatabase)
} }
@ -60,11 +60,11 @@ public extension ProfileService {
func fetchPinnedStatuses() -> AnyPublisher<Never, Error> { func fetchPinnedStatuses() -> AnyPublisher<Never, Error> {
mastodonAPIClient.request( mastodonAPIClient.request(
StatusesEndpoint.accountsStatuses( StatusesEndpoint.accountsStatuses(
id: accountID, id: id,
excludeReplies: true, excludeReplies: true,
onlyMedia: false, onlyMedia: false,
pinned: true)) pinned: true))
.flatMap { contentDatabase.insert(pinnedStatuses: $0, accountID: accountID) } .flatMap { contentDatabase.insert(pinnedStatuses: $0, accountId: id) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }

View File

@ -34,14 +34,14 @@ public extension StatusService {
func rebloggedByService() -> AccountListService { func rebloggedByService() -> AccountListService {
AccountListService( AccountListService(
endpoint: .statusRebloggedBy(id: status.id), endpoint: .rebloggedBy(id: status.id),
mastodonAPIClient: mastodonAPIClient, mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) contentDatabase: contentDatabase)
} }
func favoritedByService() -> AccountListService { func favoritedByService() -> AccountListService {
AccountListService( AccountListService(
endpoint: .statusFavouritedBy(id: status.id), endpoint: .favouritedBy(id: status.id),
mastodonAPIClient: mastodonAPIClient, mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) contentDatabase: contentDatabase)
} }

View File

@ -9,14 +9,13 @@ import MastodonAPI
public struct TimelineService { public struct TimelineService {
public let sections: AnyPublisher<[[CollectionItem]], Error> public let sections: AnyPublisher<[[CollectionItem]], Error>
public let navigationService: NavigationService public let navigationService: NavigationService
public let nextPageMaxIDs: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let title: AnyPublisher<String, Never> public let title: AnyPublisher<String, Never>
public let contextParentID: String? = nil
private let timeline: Timeline private let timeline: Timeline
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
private let nextPageMaxIDsSubject = PassthroughSubject<String, Never>() private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.timeline = timeline self.timeline = timeline
@ -24,7 +23,7 @@ public struct TimelineService {
self.contentDatabase = contentDatabase self.contentDatabase = contentDatabase
sections = contentDatabase.observation(timeline: timeline) sections = contentDatabase.observation(timeline: timeline)
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
nextPageMaxIDs = nextPageMaxIDsSubject.eraseToAnyPublisher() nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
if case let .tag(tag) = timeline { if case let .tag(tag) = timeline {
title = Just("#".appending(tag)).eraseToAnyPublisher() title = Just("#".appending(tag)).eraseToAnyPublisher()
@ -35,12 +34,12 @@ public struct TimelineService {
} }
extension TimelineService: CollectionService { extension TimelineService: CollectionService {
public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> { public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
mastodonAPIClient.pagedRequest(timeline.endpoint, maxID: maxID, minID: minID) mastodonAPIClient.pagedRequest(timeline.endpoint, maxId: maxId, minId: minId)
.handleEvents(receiveOutput: { .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) } .flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
.eraseToAnyPublisher() .eraseToAnyPublisher()

View File

@ -42,7 +42,7 @@ class InstanceURLServiceTests: XCTestCase {
updatedFilter.insert("instance.filtered") updatedFilter.insert("instance.filtered")
let updatedFilterData = try JSONEncoder().encode(updatedFilter) 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")!) StubbingURLProtocol.setStub(stub, forURL: URL(string: "https://filter.metabolist.com/filter")!)

View File

@ -74,7 +74,7 @@ class TableViewController: UITableViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
viewModel.request(maxID: nil, minID: nil) viewModel.request(maxId: nil, minId: nil)
} }
override func scrollViewDidScroll(_ scrollView: UIScrollView) { override func scrollViewDidScroll(_ scrollView: UIScrollView) {
@ -130,13 +130,13 @@ class TableViewController: UITableViewController {
extension TableViewController: UITableViewDataSourcePrefetching { extension TableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
guard guard
let maxID = viewModel.nextPageMaxID, let maxId = viewModel.nextPageMaxId,
let indexPath = indexPaths.last, let indexPath = indexPaths.last,
indexPath.section == dataSource.numberOfSections(in: tableView) - 1, indexPath.section == dataSource.numberOfSections(in: tableView) - 1,
indexPath.row == dataSource.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1 indexPath.row == dataSource.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1
else { return } else { return }
viewModel.request(maxID: maxID, minID: nil) viewModel.request(maxId: maxId, minId: nil)
} }
} }

View File

@ -14,9 +14,9 @@ import ViewModels
// swiftlint:disable force_try // swiftlint:disable force_try
let db: IdentityDatabase = { let db: IdentityDatabase = {
let id = UUID() let id = Identity.Id()
let db = try! IdentityDatabase(inMemory: true, keychain: MockKeychain.self) 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.setInstanceURL(.previewInstanceURL)
try! secrets.setAccessToken(UUID().uuidString) try! secrets.setAccessToken(UUID().uuidString)
@ -25,11 +25,11 @@ let db: IdentityDatabase = {
.receive(on: ImmediateScheduler.shared) .receive(on: ImmediateScheduler.shared)
.sink { _ in } receiveValue: { _ in } .sink { _ in } receiveValue: { _ in }
_ = db.updateInstance(.preview, forIdentityID: id) _ = db.updateInstance(.preview, id: id)
.receive(on: ImmediateScheduler.shared) .receive(on: ImmediateScheduler.shared)
.sink { _ in } receiveValue: { _ in } .sink { _ in } receiveValue: { _ in }
_ = db.updateAccount(.preview, forIdentityID: id) _ = db.updateAccount(.preview, id: id)
.receive(on: ImmediateScheduler.shared) .receive(on: ImmediateScheduler.shared)
.sink { _ in } receiveValue: { _ in } .sink { _ in } receiveValue: { _ in }

View File

@ -7,7 +7,7 @@ import ServiceLayer
final public class CollectionItemsViewModel: ObservableObject { final public class CollectionItemsViewModel: ObservableObject {
@Published public var alertItem: AlertItem? @Published public var alertItem: AlertItem?
public private(set) var nextPageMaxID: String? public private(set) var nextPageMaxId: String?
public private(set) var maintainScrollPositionOfItem: CollectionItemIdentifier? public private(set) var maintainScrollPositionOfItem: CollectionItemIdentifier?
private let items = CurrentValueSubject<[[CollectionItem]], Never>([]) private let items = CurrentValueSubject<[[CollectionItem]], Never>([])
@ -27,8 +27,8 @@ final public class CollectionItemsViewModel: ObservableObject {
.sink { _ in } .sink { _ in }
.store(in: &cancellables) .store(in: &cancellables)
collectionService.nextPageMaxIDs collectionService.nextPageMaxId
.sink { [weak self] in self?.nextPageMaxID = $0 } .sink { [weak self] in self?.nextPageMaxId = $0 }
.store(in: &cancellables) .store(in: &cancellables)
} }
} }
@ -46,8 +46,8 @@ extension CollectionItemsViewModel: CollectionViewModel {
public var navigationEvents: AnyPublisher<NavigationEvent, Never> { navigationEventsSubject.eraseToAnyPublisher() } public var navigationEvents: AnyPublisher<NavigationEvent, Never> { navigationEventsSubject.eraseToAnyPublisher() }
public func request(maxID: String? = nil, minID: String? = nil) { public func request(maxId: String? = nil, minId: String? = nil) {
collectionService.request(maxID: maxID, minID: minID) collectionService.request(maxId: maxId, minId: minId)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.handleEvents( .handleEvents(
@ -80,7 +80,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
public func canSelect(indexPath: IndexPath) -> Bool { public func canSelect(indexPath: IndexPath) -> Bool {
if case let .status(configuration) = items.value[indexPath.section][indexPath.item], if case let .status(configuration) = items.value[indexPath.section][indexPath.item],
configuration.status.id == collectionService.contextParentID { configuration.status.id == collectionService.contextParentId {
return false return false
} }
@ -102,7 +102,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
cache(viewModel: viewModel, forItem: item) cache(viewModel: viewModel, forItem: item)
} }
viewModel.isContextParent = configuration.status.id == collectionService.contextParentID viewModel.isContextParent = configuration.status.id == collectionService.contextParentId
viewModel.isPinned = configuration.pinned viewModel.isPinned = configuration.pinned
viewModel.isReplyInContext = configuration.isReplyInContext viewModel.isReplyInContext = configuration.isReplyInContext
viewModel.hasReplyFollowing = configuration.hasReplyFollowing viewModel.hasReplyFollowing = configuration.hasReplyFollowing
@ -154,12 +154,12 @@ private extension CollectionItemsViewModel {
maintainScrollPositionOfItem = nil // clear old value maintainScrollPositionOfItem = nil // clear old value
// Maintain scroll position of parent after initial load of context // Maintain scroll position of parent after initial load of context
if let contextParentID = collectionService.contextParentID { if let contextParentId = collectionService.contextParentId {
let contextParentIdentifier = CollectionItemIdentifier(id: contextParentID, kind: .status, info: [:]) let contextParentIdentifier = CollectionItemIdentifier(id: contextParentId, kind: .status, info: [:])
let onlyContextParentID = [[], [contextParentIdentifier], []] let onlyContextParentId = [[], [contextParentIdentifier], []]
if items.value.isEmpty if items.value.isEmpty
|| items.value.map({ $0.map(CollectionItemIdentifier.init(item:)) }) == onlyContextParentID { || items.value.map({ $0.map(CollectionItemIdentifier.init(item:)) }) == onlyContextParentId {
maintainScrollPositionOfItem = contextParentIdentifier maintainScrollPositionOfItem = contextParentIdentifier
} }
} }

View File

@ -9,9 +9,9 @@ public protocol CollectionViewModel {
var alertItems: AnyPublisher<AlertItem, Never> { get } var alertItems: AnyPublisher<AlertItem, Never> { get }
var loading: AnyPublisher<Bool, Never> { get } var loading: AnyPublisher<Bool, Never> { get }
var navigationEvents: AnyPublisher<NavigationEvent, Never> { get } var navigationEvents: AnyPublisher<NavigationEvent, Never> { get }
var nextPageMaxID: String? { get } var nextPageMaxId: String? { get }
var maintainScrollPositionOfItem: CollectionItemIdentifier? { get } var maintainScrollPositionOfItem: CollectionItemIdentifier? { get }
func request(maxID: String?, minID: String?) func request(maxId: String?, minId: String?)
func select(indexPath: IndexPath) func select(indexPath: IndexPath)
func canSelect(indexPath: IndexPath) -> Bool func canSelect(indexPath: IndexPath) -> Bool
func viewModel(indexPath: IndexPath) -> CollectionItemViewModel func viewModel(indexPath: IndexPath) -> CollectionItemViewModel

View File

@ -28,7 +28,7 @@ public final class EditFilterViewModel: ObservableObject {
} }
public extension EditFilterViewModel { 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 } var isSaveDisabled: Bool { filter.phrase == "" || filter.context.isEmpty }

View File

@ -5,7 +5,7 @@ import Foundation
import ServiceLayer import ServiceLayer
public final class IdentitiesViewModel: ObservableObject { public final class IdentitiesViewModel: ObservableObject {
public let currentIdentityID: UUID public let currentIdentityId: Identity.Id
@Published public var authenticated = [Identity]() @Published public var authenticated = [Identity]()
@Published public var unauthenticated = [Identity]() @Published public var unauthenticated = [Identity]()
@Published public var pending = [Identity]() @Published public var pending = [Identity]()
@ -16,7 +16,7 @@ public final class IdentitiesViewModel: ObservableObject {
public init(identification: Identification) { public init(identification: Identification) {
self.identification = identification self.identification = identification
currentIdentityID = identification.identity.id currentIdentityId = identification.identity.id
let observation = identification.service.identitiesObservation() let observation = identification.service.identitiesObservation()
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)

View File

@ -66,23 +66,23 @@ extension ProfileViewModel: CollectionViewModel {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
public var nextPageMaxID: String? { public var nextPageMaxId: String? {
collectionViewModel.value.nextPageMaxID collectionViewModel.value.nextPageMaxId
} }
public var maintainScrollPositionOfItem: CollectionItemIdentifier? { public var maintainScrollPositionOfItem: CollectionItemIdentifier? {
collectionViewModel.value.maintainScrollPositionOfItem collectionViewModel.value.maintainScrollPositionOfItem
} }
public func request(maxID: String?, minID: String?) { public func request(maxId: String?, minId: String?) {
if case .statuses = collection, maxID == nil { if case .statuses = collection, maxId == nil {
profileService.fetchPinnedStatuses() profileService.fetchPinnedStatuses()
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)
.sink { _ in } .sink { _ in }
.store(in: &cancellables) .store(in: &cancellables)
} }
collectionViewModel.value.request(maxID: maxID, minID: minID) collectionViewModel.value.request(maxId: maxId, minId: minId)
} }
public func select(indexPath: IndexPath) { public func select(indexPath: IndexPath) {

View File

@ -7,7 +7,7 @@ import ServiceLayer
public final class RootViewModel: ObservableObject { public final class RootViewModel: ObservableObject {
@Published public private(set) var navigationViewModel: NavigationViewModel? @Published public private(set) var navigationViewModel: NavigationViewModel?
@Published private var mostRecentlyUsedIdentityID: UUID? @Published private var mostRecentlyUsedIdentityId: Identity.Id?
private let environment: AppEnvironment private let environment: AppEnvironment
private let allIdentitiesService: AllIdentitiesService private let allIdentitiesService: AllIdentitiesService
private let userNotificationService: UserNotificationService private let userNotificationService: UserNotificationService
@ -21,11 +21,11 @@ public final class RootViewModel: ObservableObject {
userNotificationService = UserNotificationService(environment: environment) userNotificationService = UserNotificationService(environment: environment)
self.registerForRemoteNotifications = registerForRemoteNotifications self.registerForRemoteNotifications = registerForRemoteNotifications
allIdentitiesService.immediateMostRecentlyUsedIdentityIDObservation() allIdentitiesService.immediateMostRecentlyUsedIdentityIdObservation()
.replaceError(with: nil) .replaceError(with: nil)
.assign(to: &$mostRecentlyUsedIdentityID) .assign(to: &$mostRecentlyUsedIdentityId)
identitySelected(id: mostRecentlyUsedIdentityID, immediate: true) identitySelected(id: mostRecentlyUsedIdentityId, immediate: true)
allIdentitiesService.identitiesCreated allIdentitiesService.identitiesCreated
.sink { [weak self] in self?.identitySelected(id: $0) } .sink { [weak self] in self?.identitySelected(id: $0) }
@ -42,11 +42,11 @@ public final class RootViewModel: ObservableObject {
} }
public extension RootViewModel { public extension RootViewModel {
func identitySelected(id: UUID?) { func identitySelected(id: Identity.Id?) {
identitySelected(id: id, immediate: false) identitySelected(id: id, immediate: false)
} }
func deleteIdentity(id: UUID) { func deleteIdentity(id: Identity.Id) {
allIdentitiesService.deleteIdentity(id: id) allIdentitiesService.deleteIdentity(id: id)
.sink { _ in } receiveValue: { _ in } .sink { _ in } receiveValue: { _ in }
.store(in: &cancellables) .store(in: &cancellables)
@ -60,7 +60,7 @@ public extension RootViewModel {
} }
private extension RootViewModel { private extension RootViewModel {
func identitySelected(id: UUID?, immediate: Bool) { func identitySelected(id: Identity.Id?, immediate: Bool) {
navigationViewModel?.presentingSecondaryNavigation = false navigationViewModel?.presentingSecondaryNavigation = false
guard guard
@ -74,7 +74,7 @@ private extension RootViewModel {
let observation = identityService.observation(immediate: immediate) let observation = identityService.observation(immediate: immediate)
.catch { [weak self] _ -> Empty<Identity, Never> in .catch { [weak self] _ -> Empty<Identity, Never> in
DispatchQueue.main.async { DispatchQueue.main.async {
self?.identitySelected(id: self?.mostRecentlyUsedIdentityID, immediate: false) self?.identitySelected(id: self?.mostRecentlyUsedIdentityId, immediate: false)
} }
return Empty() return Empty()

View File

@ -18,12 +18,12 @@ class AddIdentityViewModelTests: XCTestCase {
let sut = AddIdentityViewModel( let sut = AddIdentityViewModel(
allIdentitiesService: allIdentitiesService, allIdentitiesService: allIdentitiesService,
instanceURLService: InstanceURLService(environment: environment)) instanceURLService: InstanceURLService(environment: environment))
let addedIDRecorder = allIdentitiesService.identitiesCreated.record() let addedIdRecorder = allIdentitiesService.identitiesCreated.record()
sut.urlFieldText = "https://mastodon.social" sut.urlFieldText = "https://mastodon.social"
sut.logInTapped() sut.logInTapped()
_ = try wait(for: addedIDRecorder.next(), timeout: 1) _ = try wait(for: addedIdRecorder.next(), timeout: 1)
} }
func testAddIdentityWithoutScheme() throws { func testAddIdentityWithoutScheme() throws {
@ -33,12 +33,12 @@ class AddIdentityViewModelTests: XCTestCase {
let sut = AddIdentityViewModel( let sut = AddIdentityViewModel(
allIdentitiesService: allIdentitiesService, allIdentitiesService: allIdentitiesService,
instanceURLService: InstanceURLService(environment: environment)) instanceURLService: InstanceURLService(environment: environment))
let addedIDRecorder = allIdentitiesService.identitiesCreated.record() let addedIdRecorder = allIdentitiesService.identitiesCreated.record()
sut.urlFieldText = "mastodon.social" sut.urlFieldText = "mastodon.social"
sut.logInTapped() sut.logInTapped()
_ = try wait(for: addedIDRecorder.next(), timeout: 1) _ = try wait(for: addedIdRecorder.next(), timeout: 1)
} }
func testInvalidURL() throws { func testInvalidURL() throws {

View File

@ -81,7 +81,7 @@ private extension AccountHeaderView {
segmentedControl.insertSegment( segmentedControl.insertSegment(
action: UIAction(title: collection.title) { [weak self] _ in action: UIAction(title: collection.title) { [weak self] _ in
self?.viewModel?.collection = collection self?.viewModel?.collection = collection
self?.viewModel?.request(maxID: nil, minID: nil) self?.viewModel?.request(maxId: nil, minId: nil)
}, },
at: index, at: index,
animated: false) animated: false)

View File

@ -47,7 +47,7 @@ private extension IdentitiesView {
} label: { } label: {
row(identity: identity) row(identity: identity)
} }
.disabled(identity.id == viewModel.currentIdentityID) .disabled(identity.id == viewModel.currentIdentityId)
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
} }
.onDelete { .onDelete {
@ -94,7 +94,7 @@ private extension IdentitiesView {
Spacer() Spacer()
} }
Spacer() Spacer()
if identity.id == viewModel.currentIdentityID { if identity.id == viewModel.currentIdentityId {
Image(systemName: "checkmark.circle") Image(systemName: "checkmark.circle")
} }
} }