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:
- identifier_name
- type_name
# Swift 5.3
- multiple_closures_with_trailing_closure
- no_space_in_method_call

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,12 +4,12 @@ import Foundation
import Mastodon
public struct IdentityFixture {
public let id: UUID
public let id: Identity.Id
public let instanceURL: URL
public let instance: Instance?
public let account: Account?
public init(id: UUID, instanceURL: URL, instance: Instance?, account: Account?) {
public init(id: Identity.Id, instanceURL: URL, instance: Instance?, account: Account?) {
self.id = id
self.instanceURL = instanceURL
self.instance = instance

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -29,7 +29,8 @@ extension AuthenticationService {
.eraseToAnyPublisher()
}
func register(_ registration: Registration, id: UUID) -> AnyPublisher<(AppAuthorization, AccessToken), Error> {
func register(_ registration: Registration,
id: Identity.Id) -> AnyPublisher<(AppAuthorization, AccessToken), Error> {
let redirectURI = OAuth.registrationCallbackURL.appendingPathComponent(id.uuidString)
let authorization = appAuthorization(redirectURI: redirectURI)
.share()
@ -38,7 +39,7 @@ extension AuthenticationService {
authorization.flatMap { appAuthorization -> AnyPublisher<AccessToken, Error> in
mastodonAPIClient.request(
AccessTokenEndpoint.oauthToken(
clientID: appAuthorization.clientId,
clientId: appAuthorization.clientId,
clientSecret: appAuthorization.clientSecret,
grantType: OAuth.registrationGrantType,
scopes: OAuth.scopes,
@ -134,7 +135,7 @@ private extension AuthenticationService {
.flatMap {
mastodonAPIClient.request(
AccessTokenEndpoint.oauthToken(
clientID: appAuthorization.clientId,
clientId: appAuthorization.clientId,
clientSecret: appAuthorization.clientSecret,
grantType: OAuth.authorizationCodeGrantType,
scopes: OAuth.scopes,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,7 +42,7 @@ class InstanceURLServiceTests: XCTestCase {
updatedFilter.insert("instance.filtered")
let updatedFilterData = try JSONEncoder().encode(updatedFilter)
let stub: HTTPStub = .success((URLResponse(), updatedFilterData))
let stub: HTTPStub = .success((HTTPURLResponse(), updatedFilterData))
StubbingURLProtocol.setStub(stub, forURL: URL(string: "https://filter.metabolist.com/filter")!)

View file

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

View file

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

View file

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

View file

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

View file

@ -28,7 +28,7 @@ public final class EditFilterViewModel: ObservableObject {
}
public extension EditFilterViewModel {
var isNew: Bool { filter.id == Filter.newFilterID }
var isNew: Bool { filter.id == Filter.newFilterId }
var isSaveDisabled: Bool { filter.phrase == "" || filter.context.isEmpty }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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