diff --git a/DB/Sources/DB/Content/AccountRecord.swift b/DB/Sources/DB/Content/AccountRecord.swift index 75881e2..1c8bea3 100644 --- a/DB/Sources/DB/Content/AccountRecord.swift +++ b/DB/Sources/DB/Content/AccountRecord.swift @@ -56,6 +56,7 @@ extension AccountRecord { static let moved = belongsTo(AccountRecord.self) static let relationship = hasOne(Relationship.self) static let identityProofs = hasMany(IdentityProofRecord.self) + static let featuredTags = hasMany(FeaturedTagRecord.self) static let pinnedStatusJoins = hasMany(AccountPinnedStatusJoin.self) .order(AccountPinnedStatusJoin.Columns.index) static let pinnedStatuses = hasMany( diff --git a/DB/Sources/DB/Content/ContentDatabase+Migration.swift b/DB/Sources/DB/Content/ContentDatabase+Migration.swift index e493f56..c16bb05 100644 --- a/DB/Sources/DB/Content/ContentDatabase+Migration.swift +++ b/DB/Sources/DB/Content/ContentDatabase+Migration.swift @@ -57,6 +57,15 @@ extension ContentDatabase { t.primaryKey(["accountId", "provider"], onConflict: .replace) } + try db.create(table: "featuredTagRecord") { t in + t.column("id", .text).primaryKey(onConflict: .replace) + t.column("name", .text).notNull() + t.column("url", .text).notNull() + t.column("statusesCount", .integer).notNull() + t.column("lastStatusAt", .date).notNull() + t.column("accountId", .text).notNull().references("accountRecord", onDelete: .cascade) + } + try db.create(table: "statusRecord") { t in t.column("id", .text).primaryKey(onConflict: .replace) t.column("uri", .text).notNull() diff --git a/DB/Sources/DB/Content/ContentDatabase.swift b/DB/Sources/DB/Content/ContentDatabase.swift index f525739..bb5b66f 100644 --- a/DB/Sources/DB/Content/ContentDatabase.swift +++ b/DB/Sources/DB/Content/ContentDatabase.swift @@ -284,6 +284,23 @@ public extension ContentDatabase { .eraseToAnyPublisher() } + func insert(featuredTags: [FeaturedTag], id: Account.Id) -> AnyPublisher { + databaseWriter.writePublisher { + for featuredTag in featuredTags { + try FeaturedTagRecord( + id: featuredTag.id, + name: featuredTag.name, + url: featuredTag.url, + statusesCount: featuredTag.statusesCount, + lastStatusAt: featuredTag.lastStatusAt, + accountId: id) + .save($0) + } + } + .ignoreOutput() + .eraseToAnyPublisher() + } + func insert(relationships: [Relationship]) -> AnyPublisher { databaseWriter.writePublisher { for relationship in relationships { diff --git a/DB/Sources/DB/Content/FeaturedTagRecord.swift b/DB/Sources/DB/Content/FeaturedTagRecord.swift new file mode 100644 index 0000000..c577a7e --- /dev/null +++ b/DB/Sources/DB/Content/FeaturedTagRecord.swift @@ -0,0 +1,25 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import GRDB +import Mastodon + +struct FeaturedTagRecord: ContentDatabaseRecord, Hashable { + let id: FeaturedTag.Id + let name: String + let url: URL + let statusesCount: Int + let lastStatusAt: Date + let accountId: Account.Id +} + +extension FeaturedTagRecord { + enum Columns { + static let id = Column(CodingKeys.id) + static let name = Column(CodingKeys.name) + static let url = Column(CodingKeys.url) + static let statusesCount = Column(CodingKeys.statusesCount) + static let lastStatusAt = Column(CodingKeys.lastStatusAt) + static let accountId = Column(CodingKeys.accountId) + } +} diff --git a/DB/Sources/DB/Content/ProfileInfo.swift b/DB/Sources/DB/Content/ProfileInfo.swift index 25e6515..75d4965 100644 --- a/DB/Sources/DB/Content/ProfileInfo.swift +++ b/DB/Sources/DB/Content/ProfileInfo.swift @@ -8,6 +8,7 @@ struct ProfileInfo: Codable, Hashable, FetchableRecord { let accountInfo: AccountInfo let relationship: Relationship? let identityProofRecords: [IdentityProofRecord] + let featuredTagRecords: [FeaturedTagRecord] } extension ProfileInfo { @@ -15,6 +16,7 @@ extension ProfileInfo { AccountInfo.addingIncludes(request) .including(optional: AccountRecord.relationship.forKey(CodingKeys.relationship)) .including(all: AccountRecord.identityProofs.forKey(CodingKeys.identityProofRecords)) + .including(all: AccountRecord.featuredTags.forKey(CodingKeys.featuredTagRecords)) } static func request(_ request: QueryInterfaceRequest) -> QueryInterfaceRequest { diff --git a/DB/Sources/DB/Entities/Profile.swift b/DB/Sources/DB/Entities/Profile.swift index 927f856..d258374 100644 --- a/DB/Sources/DB/Entities/Profile.swift +++ b/DB/Sources/DB/Entities/Profile.swift @@ -7,11 +7,13 @@ public struct Profile: Codable, Hashable { public let account: Account public let relationship: Relationship? public let identityProofs: [IdentityProof] + public let featuredTags: [FeaturedTag] public init(account: Account) { self.account = account self.relationship = nil self.identityProofs = [] + self.featuredTags = [] } } @@ -20,5 +22,6 @@ extension Profile { account = Account(info: info.accountInfo) relationship = info.relationship identityProofs = info.identityProofRecords.map(IdentityProof.init(record:)) + featuredTags = info.featuredTagRecords.map(FeaturedTag.init(record:)) } } diff --git a/DB/Sources/DB/Extensions/FeaturedTag+Extensions.swift b/DB/Sources/DB/Extensions/FeaturedTag+Extensions.swift new file mode 100644 index 0000000..1d40a76 --- /dev/null +++ b/DB/Sources/DB/Extensions/FeaturedTag+Extensions.swift @@ -0,0 +1,16 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import GRDB +import Mastodon + +extension FeaturedTag { + init(record: FeaturedTagRecord) { + self.init( + id: record.id, + name: record.name, + url: record.url, + statusesCount: record.statusesCount, + lastStatusAt: record.lastStatusAt) + } +} diff --git a/Mastodon/Sources/Mastodon/Entities/FeaturedTag.swift b/Mastodon/Sources/Mastodon/Entities/FeaturedTag.swift new file mode 100644 index 0000000..df0275a --- /dev/null +++ b/Mastodon/Sources/Mastodon/Entities/FeaturedTag.swift @@ -0,0 +1,23 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation + +public struct FeaturedTag: Codable, Hashable { + public let id: Id + public let name: String + public let url: URL + public let statusesCount: Int + public let lastStatusAt: Date + + public init(id: FeaturedTag.Id, name: String, url: URL, statusesCount: Int, lastStatusAt: Date) { + self.id = id + self.name = name + self.url = url + self.statusesCount = statusesCount + self.lastStatusAt = lastStatusAt + } +} + +public extension FeaturedTag { + typealias Id = String +} diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/FeaturedTagsEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/FeaturedTagsEndpoint.swift new file mode 100644 index 0000000..9072b9d --- /dev/null +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/FeaturedTagsEndpoint.swift @@ -0,0 +1,35 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation +import HTTP +import Mastodon + +public enum FeaturedTagsEndpoint { + case featuredTags(id: Account.Id) +} + +extension FeaturedTagsEndpoint: Endpoint { + public typealias ResultType = [FeaturedTag] + + public var context: [String] { + switch self { + case .featuredTags: + return defaultContext + ["accounts"] + } + + } + + public var pathComponentsInContext: [String] { + switch self { + case let .featuredTags(id): + return [id, "featured_tags"] + } + } + + public var method: HTTPMethod { + switch self { + case .featuredTags: + return .get + } + } +} diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift index d78af80..4046c65 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AccountService.swift @@ -10,6 +10,7 @@ public struct AccountService { public let account: Account public let relationship: Relationship? public let identityProofs: [IdentityProof] + public let featuredTags: [FeaturedTag] public let navigationService: NavigationService private let mastodonAPIClient: MastodonAPIClient @@ -18,11 +19,13 @@ public struct AccountService { public init(account: Account, relationship: Relationship? = nil, identityProofs: [IdentityProof] = [], + featuredTags: [FeaturedTag] = [], mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { self.account = account self.relationship = relationship self.identityProofs = identityProofs + self.featuredTags = featuredTags navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) self.mastodonAPIClient = mastodonAPIClient self.contentDatabase = contentDatabase diff --git a/ServiceLayer/Sources/ServiceLayer/Services/EmojiPickerService.swift b/ServiceLayer/Sources/ServiceLayer/Services/EmojiPickerService.swift index 528969f..6cfa20b 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/EmojiPickerService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/EmojiPickerService.swift @@ -103,7 +103,6 @@ public extension EmojiPickerService { promise(.failure(error)) } } - .print() .eraseToAnyPublisher() } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift b/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift index 37d3273..4725a3e 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/ProfileService.swift @@ -49,6 +49,7 @@ public struct ProfileService { account: $0.account, relationship: $0.relationship, identityProofs: $0.identityProofs, + featuredTags: $0.featuredTags, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) } @@ -65,14 +66,17 @@ public extension ProfileService { } func fetchProfile() -> AnyPublisher { - Publishers.Merge3( + Publishers.Merge4( mastodonAPIClient.request(AccountEndpoint.accounts(id: id)) .flatMap { contentDatabase.insert(accounts: [$0]) }, mastodonAPIClient.request(RelationshipsEndpoint.relationships(ids: [id])) .flatMap { contentDatabase.insert(relationships: $0) }, mastodonAPIClient.request(IdentityProofsEndpoint.identityProofs(id: id)) .catch { _ in Empty() } - .flatMap { contentDatabase.insert(identityProofs: $0, id: id) }) + .flatMap { contentDatabase.insert(identityProofs: $0, id: id) }, + mastodonAPIClient.request(FeaturedTagsEndpoint.featuredTags(id: id)) + .catch { _ in Empty() } + .flatMap { contentDatabase.insert(featuredTags: $0, id: id) }) .eraseToAnyPublisher() } diff --git a/ViewModels/Sources/ViewModels/AccountViewModel.swift b/ViewModels/Sources/ViewModels/AccountViewModel.swift index e73392b..4037fb3 100644 --- a/ViewModels/Sources/ViewModels/AccountViewModel.swift +++ b/ViewModels/Sources/ViewModels/AccountViewModel.swift @@ -44,6 +44,8 @@ public extension AccountViewModel { var identityProofs: [IdentityProof] { accountService.identityProofs } + var featuredTags: [FeaturedTag] { accountService.featuredTags } + var fields: [Account.Field] { accountService.account.fields } var note: NSAttributedString { accountService.account.note.attributed }