diff --git a/DB/Sources/DB/Content/ContentDatabase+Migration.swift b/DB/Sources/DB/Content/ContentDatabase+Migration.swift index 0ef1404..87f2779 100644 --- a/DB/Sources/DB/Content/ContentDatabase+Migration.swift +++ b/DB/Sources/DB/Content/ContentDatabase+Migration.swift @@ -150,6 +150,21 @@ extension ContentDatabase { t.column("count", .integer).notNull() } + try db.create(table: "announcement") { t in + t.column("id", .text).primaryKey(onConflict: .replace) + t.column("content", .text).notNull() + t.column("startsAt", .datetime) + t.column("endsAt", .datetime) + t.column("allDay", .boolean).notNull() + t.column("publishedAt", .datetime).notNull() + t.column("updatedAt", .datetime).notNull() + t.column("read", .boolean).notNull() + t.column("mentions", .blob).notNull() + t.column("tags", .blob).notNull() + t.column("emojis", .blob).notNull() + t.column("reactions", .blob).notNull() + } + try db.create(table: "conversationRecord") { t in t.column("id", .text).primaryKey(onConflict: .replace) t.column("unread", .boolean).notNull() diff --git a/DB/Sources/DB/Content/ContentDatabase.swift b/DB/Sources/DB/Content/ContentDatabase.swift index 0a1a60e..14585c3 100644 --- a/DB/Sources/DB/Content/ContentDatabase.swift +++ b/DB/Sources/DB/Content/ContentDatabase.swift @@ -407,6 +407,18 @@ public extension ContentDatabase { .eraseToAnyPublisher() } + func update(announcements: [Announcement]) -> AnyPublisher { + databaseWriter.writePublisher { + for announcement in announcements { + try announcement.save($0) + } + + try Announcement.filter(!announcements.map(\.id).contains(Announcement.Columns.id)).deleteAll($0) + } + .ignoreOutput() + .eraseToAnyPublisher() + } + func timelinePublisher(_ timeline: Timeline) -> AnyPublisher<[[CollectionItem]], Error> { ValueObservation.tracking( TimelineItemsInfo.request(TimelineRecord.filter(TimelineRecord.Columns.id == timeline.id)).fetchOne) diff --git a/DB/Sources/DB/Extensions/Announcement+Extensions.swift b/DB/Sources/DB/Extensions/Announcement+Extensions.swift new file mode 100644 index 0000000..4b3042d --- /dev/null +++ b/DB/Sources/DB/Extensions/Announcement+Extensions.swift @@ -0,0 +1,24 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation +import GRDB +import Mastodon + +extension Announcement: ContentDatabaseRecord {} + +extension Announcement { + enum Columns: String, ColumnExpression { + case id + case content + case startsAt + case endsAt + case allDay + case publishedAt + case updatedAt + case read + case mentions + case tags + case emojis + case reactions + } +} diff --git a/Mastodon/Sources/Mastodon/Entities/Announcement.swift b/Mastodon/Sources/Mastodon/Entities/Announcement.swift new file mode 100644 index 0000000..0e74f82 --- /dev/null +++ b/Mastodon/Sources/Mastodon/Entities/Announcement.swift @@ -0,0 +1,18 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation + +public struct Announcement: Codable, Hashable { + public let id: String + public let content: HTML + public let startsAt: Date? + public let endsAt: Date? + public let allDay: Bool + public let publishedAt: Date + public let updatedAt: Date + public let read: Bool + public let mentions: [Mention] + public let tags: [Tag] + public let emojis: [Emoji] + public let reactions: [AnnouncementReaction] +} diff --git a/Mastodon/Sources/Mastodon/Entities/AnnouncementReaction.swift b/Mastodon/Sources/Mastodon/Entities/AnnouncementReaction.swift new file mode 100644 index 0000000..51c3558 --- /dev/null +++ b/Mastodon/Sources/Mastodon/Entities/AnnouncementReaction.swift @@ -0,0 +1,11 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation + +public struct AnnouncementReaction: Codable, Hashable { + public let name: String + public let count: Int + public let me: Bool + public let url: URL? + public let staticUrl: URL? +} diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/AnnouncementEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/AnnouncementEndpoint.swift new file mode 100644 index 0000000..dd3c7ea --- /dev/null +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/AnnouncementEndpoint.swift @@ -0,0 +1,21 @@ +// Copyright © 2021 Metabolist. All rights reserved. + +import Foundation +import HTTP +import Mastodon + +public enum AnnouncementsEndpoint { + case announcements +} + +extension AnnouncementsEndpoint: Endpoint { + public typealias ResultType = [Announcement] + + public var pathComponentsInContext: [String] { + ["announcements"] + } + + public var method: HTTPMethod { + .get + } +} diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index d57d4d0..3ad54e6 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -68,6 +68,13 @@ public extension IdentityService { .eraseToAnyPublisher() } + func refreshAnnouncements() -> AnyPublisher { + mastodonAPIClient.request(AnnouncementsEndpoint.announcements) + .flatMap(contentDatabase.update(announcements:)) + .print() + .eraseToAnyPublisher() + } + func confirmIdentity() -> AnyPublisher { identityDatabase.confirmIdentity(id: id) } diff --git a/ViewModels/Sources/ViewModels/NavigationViewModel.swift b/ViewModels/Sources/ViewModels/NavigationViewModel.swift index c5e834d..c358fb7 100644 --- a/ViewModels/Sources/ViewModels/NavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/NavigationViewModel.swift @@ -123,6 +123,9 @@ public extension NavigationViewModel { identification.service.refreshEmojis() .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) + identification.service.refreshAnnouncements() + .sink { _ in } receiveValue: { _ in } + .store(in: &cancellables) if identification.identity.preferences.useServerPostingReadingPreferences { identification.service.refreshServerPreferences()