2020-07-29 23:50:30 +00:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Combine
|
2020-09-05 02:31:43 +00:00
|
|
|
import Foundation
|
2020-07-29 23:50:30 +00:00
|
|
|
import GRDB
|
2020-09-04 06:12:06 +00:00
|
|
|
import Keychain
|
2020-08-30 23:33:11 +00:00
|
|
|
import Mastodon
|
2020-09-04 06:12:06 +00:00
|
|
|
import Secrets
|
2020-07-29 23:50:30 +00:00
|
|
|
|
2020-09-03 03:28:34 +00:00
|
|
|
public enum IdentityDatabaseError: Error {
|
2020-08-03 15:20:51 +00:00
|
|
|
case identityNotFound
|
|
|
|
}
|
|
|
|
|
2020-09-03 03:28:34 +00:00
|
|
|
public struct IdentityDatabase {
|
2020-09-28 05:16:02 +00:00
|
|
|
private let databaseWriter: DatabaseWriter
|
2020-07-29 23:50:30 +00:00
|
|
|
|
2020-09-08 02:12:38 +00:00
|
|
|
public init(inMemory: Bool, keychain: Keychain.Type) throws {
|
2020-09-03 03:28:34 +00:00
|
|
|
if inMemory {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter = DatabaseQueue()
|
2020-07-29 23:50:30 +00:00
|
|
|
} else {
|
2020-09-04 06:12:06 +00:00
|
|
|
let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path
|
|
|
|
var configuration = Configuration()
|
2020-09-03 01:14:33 +00:00
|
|
|
|
2020-09-07 14:25:26 +00:00
|
|
|
configuration.prepareDatabase {
|
2020-10-05 22:50:05 +00:00
|
|
|
try $0.usePassphrase(Secrets.databaseKey(identityId: nil, keychain: keychain))
|
2020-09-04 06:12:06 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 06:05:08 +00:00
|
|
|
try migrator.migrate(databaseWriter)
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 03:28:34 +00:00
|
|
|
public extension IdentityDatabase {
|
2020-10-05 22:50:05 +00:00
|
|
|
func createIdentity(id: Identity.Id, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher(
|
2020-09-05 02:05:15 +00:00
|
|
|
updates: IdentityRecord(
|
2020-08-04 20:26:09 +00:00
|
|
|
id: id,
|
|
|
|
url: url,
|
2020-09-09 05:40:49 +00:00
|
|
|
authenticated: authenticated,
|
2020-09-13 08:03:08 +00:00
|
|
|
pending: pending,
|
2020-08-04 20:26:09 +00:00
|
|
|
lastUsedAt: Date(),
|
2020-08-07 01:41:59 +00:00
|
|
|
preferences: Identity.Preferences(),
|
2020-08-12 07:24:39 +00:00
|
|
|
instanceURI: nil,
|
2020-08-14 01:24:53 +00:00
|
|
|
lastRegisteredDeviceToken: nil,
|
|
|
|
pushSubscriptionAlerts: .initial)
|
2020-08-12 07:24:39 +00:00
|
|
|
.save)
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-02 07:02:03 +00:00
|
|
|
.eraseToAnyPublisher()
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-29 06:06:25 +00:00
|
|
|
databaseWriter.writePublisher(updates: IdentityRecord.filter(IdentityRecord.Columns.id == id).deleteAll)
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-08 07:43:06 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func updateLastUsedAt(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-05 02:05:15 +00:00
|
|
|
try IdentityRecord
|
2020-10-05 22:50:05 +00:00
|
|
|
.filter(IdentityRecord.Columns.id == id)
|
2020-09-29 06:06:25 +00:00
|
|
|
.updateAll($0, IdentityRecord.Columns.lastUsedAt.set(to: Date()))
|
2020-08-04 20:26:09 +00:00
|
|
|
}
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-04 20:26:09 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func updateInstance(_ instance: Instance, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-07-29 23:50:30 +00:00
|
|
|
try Identity.Instance(
|
|
|
|
uri: instance.uri,
|
|
|
|
streamingAPI: instance.urls.streamingApi,
|
|
|
|
title: instance.title,
|
|
|
|
thumbnail: instance.thumbnail)
|
|
|
|
.save($0)
|
2020-09-05 02:05:15 +00:00
|
|
|
try IdentityRecord
|
2020-10-05 22:50:05 +00:00
|
|
|
.filter(IdentityRecord.Columns.id == id)
|
2020-09-29 06:06:25 +00:00
|
|
|
.updateAll($0, IdentityRecord.Columns.instanceURI.set(to: instance.uri))
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-07-29 23:50:30 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func updateAccount(_ account: Account, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher(
|
2020-08-02 07:02:03 +00:00
|
|
|
updates: Identity.Account(
|
2020-07-29 23:50:30 +00:00
|
|
|
id: account.id,
|
2020-10-05 22:50:05 +00:00
|
|
|
identityId: id,
|
2020-07-29 23:50:30 +00:00
|
|
|
username: account.username,
|
2020-08-08 09:10:05 +00:00
|
|
|
displayName: account.displayName,
|
2020-07-29 23:50:30 +00:00
|
|
|
url: account.url,
|
|
|
|
avatar: account.avatar,
|
|
|
|
avatarStatic: account.avatarStatic,
|
|
|
|
header: account.header,
|
2020-08-08 09:10:05 +00:00
|
|
|
headerStatic: account.headerStatic,
|
|
|
|
emojis: account.emojis)
|
2020-08-02 07:02:03 +00:00
|
|
|
.save)
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-02 07:02:03 +00:00
|
|
|
.eraseToAnyPublisher()
|
2020-08-07 10:14:14 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func confirmIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-13 08:03:08 +00:00
|
|
|
try IdentityRecord
|
2020-09-29 06:06:25 +00:00
|
|
|
.filter(IdentityRecord.Columns.id == id)
|
|
|
|
.updateAll($0, IdentityRecord.Columns.pending.set(to: false))
|
2020-09-13 08:03:08 +00:00
|
|
|
}
|
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func updatePreferences(_ preferences: Mastodon.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-10-05 22:50:05 +00:00
|
|
|
guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == id)
|
2020-09-07 16:33:36 +00:00
|
|
|
.fetchOne($0)?
|
|
|
|
.preferences else {
|
|
|
|
throw IdentityDatabaseError.identityNotFound
|
|
|
|
}
|
2020-08-07 10:14:14 +00:00
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
try Self.writePreferences(storedPreferences.updated(from: preferences), id: id)($0)
|
2020-08-07 10:14:14 +00:00
|
|
|
}
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-07 10:14:14 +00:00
|
|
|
.eraseToAnyPublisher()
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func updatePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
|
|
|
|
databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: id))
|
2020-09-09 12:05:43 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
2020-09-07 16:33:36 +00:00
|
|
|
}
|
|
|
|
|
2020-08-14 21:41:55 +00:00
|
|
|
func updatePushSubscription(alerts: PushSubscription.Alerts,
|
2020-09-06 21:37:54 +00:00
|
|
|
deviceToken: Data? = nil,
|
2020-10-05 22:50:05 +00:00
|
|
|
id: Identity.Id) -> AnyPublisher<Never, Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-29 06:06:25 +00:00
|
|
|
let data = try IdentityRecord.databaseJSONEncoder(
|
|
|
|
for: IdentityRecord.Columns.pushSubscriptionAlerts.name)
|
|
|
|
.encode(alerts)
|
2020-08-12 07:24:39 +00:00
|
|
|
|
2020-09-05 02:05:15 +00:00
|
|
|
try IdentityRecord
|
2020-10-05 22:50:05 +00:00
|
|
|
.filter(IdentityRecord.Columns.id == id)
|
2020-09-29 06:06:25 +00:00
|
|
|
.updateAll($0, IdentityRecord.Columns.pushSubscriptionAlerts.set(to: data))
|
2020-08-12 07:24:39 +00:00
|
|
|
|
2020-08-14 21:41:55 +00:00
|
|
|
if let deviceToken = deviceToken {
|
2020-09-05 02:05:15 +00:00
|
|
|
try IdentityRecord
|
2020-10-05 22:50:05 +00:00
|
|
|
.filter(IdentityRecord.Columns.id == id)
|
2020-09-29 06:06:25 +00:00
|
|
|
.updateAll($0, IdentityRecord.Columns.lastRegisteredDeviceToken.set(to: deviceToken))
|
2020-08-14 21:41:55 +00:00
|
|
|
}
|
2020-08-12 07:24:39 +00:00
|
|
|
}
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-12 07:24:39 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func identityObservation(id: Identity.Id, immediate: Bool) -> AnyPublisher<Identity, Error> {
|
2020-08-02 07:02:03 +00:00
|
|
|
ValueObservation.tracking(
|
2020-09-29 23:56:09 +00:00
|
|
|
IdentityInfo.request(IdentityRecord.filter(IdentityRecord.Columns.id == id)).fetchOne)
|
2020-08-02 07:02:03 +00:00
|
|
|
.removeDuplicates()
|
2020-09-28 05:16:02 +00:00
|
|
|
.publisher(in: databaseWriter, scheduling: immediate ? .immediate : .async(onQueue: .main))
|
2020-08-03 15:20:51 +00:00
|
|
|
.tryMap {
|
2020-09-29 23:56:09 +00:00
|
|
|
guard let info = $0 else { throw IdentityDatabaseError.identityNotFound }
|
2020-08-02 07:02:03 +00:00
|
|
|
|
2020-09-29 23:56:09 +00:00
|
|
|
return Identity(info: info)
|
2020-08-02 07:02:03 +00:00
|
|
|
}
|
|
|
|
.eraseToAnyPublisher()
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
2020-08-04 20:26:09 +00:00
|
|
|
|
2020-08-08 07:43:06 +00:00
|
|
|
func identitiesObservation() -> AnyPublisher<[Identity], Error> {
|
2020-09-09 12:05:43 +00:00
|
|
|
ValueObservation.tracking(
|
2020-09-29 23:56:09 +00:00
|
|
|
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc)).fetchAll)
|
2020-08-04 20:26:09 +00:00
|
|
|
.removeDuplicates()
|
2020-09-28 05:16:02 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-09-29 23:56:09 +00:00
|
|
|
.map { $0.map(Identity.init(info:)) }
|
2020-08-04 20:26:09 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func recentIdentitiesObservation(excluding: Identity.Id) -> AnyPublisher<[Identity], Error> {
|
2020-08-08 07:43:06 +00:00
|
|
|
ValueObservation.tracking(
|
2020-09-29 23:56:09 +00:00
|
|
|
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
|
2020-09-29 06:06:25 +00:00
|
|
|
.filter(IdentityRecord.Columns.id != excluding)
|
2020-08-08 07:43:06 +00:00
|
|
|
.limit(9)
|
|
|
|
.fetchAll)
|
2020-08-04 20:26:09 +00:00
|
|
|
.removeDuplicates()
|
2020-09-28 05:16:02 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-09-29 23:56:09 +00:00
|
|
|
.map { $0.map(Identity.init(info:)) }
|
2020-08-04 20:26:09 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
func immediateMostRecentlyUsedIdentityIdObservation() -> AnyPublisher<Identity.Id?, Error> {
|
2020-09-29 06:06:25 +00:00
|
|
|
ValueObservation.tracking(
|
|
|
|
IdentityRecord.select(IdentityRecord.Columns.id)
|
|
|
|
.order(IdentityRecord.Columns.lastUsedAt.desc).fetchOne)
|
2020-08-09 05:37:04 +00:00
|
|
|
.removeDuplicates()
|
2020-09-28 05:16:02 +00:00
|
|
|
.publisher(in: databaseWriter, scheduling: .immediate)
|
2020-08-09 05:37:04 +00:00
|
|
|
.eraseToAnyPublisher()
|
2020-08-04 20:26:09 +00:00
|
|
|
}
|
2020-08-12 07:24:39 +00:00
|
|
|
|
2020-09-06 21:37:54 +00:00
|
|
|
func identitiesWithOutdatedDeviceTokens(deviceToken: Data) -> AnyPublisher<[Identity], Error> {
|
2020-09-28 05:16:02 +00:00
|
|
|
databaseWriter.readPublisher(
|
2020-09-29 23:56:09 +00:00
|
|
|
value: IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
|
2020-09-29 06:06:25 +00:00
|
|
|
.filter(IdentityRecord.Columns.lastRegisteredDeviceToken != deviceToken)
|
2020-08-12 07:24:39 +00:00
|
|
|
.fetchAll)
|
2020-09-29 23:56:09 +00:00
|
|
|
.map { $0.map(Identity.init(info:)) }
|
2020-08-12 07:24:39 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private extension IdentityDatabase {
|
2020-09-28 06:05:08 +00:00
|
|
|
static let name = "identity"
|
2020-09-04 06:12:06 +00:00
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
static func writePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> (Database) throws -> Void {
|
2020-09-07 16:33:36 +00:00
|
|
|
{
|
2020-09-29 06:06:25 +00:00
|
|
|
let data = try IdentityRecord.databaseJSONEncoder(
|
|
|
|
for: IdentityRecord.Columns.preferences.name).encode(preferences)
|
2020-09-07 16:33:36 +00:00
|
|
|
|
|
|
|
try IdentityRecord
|
2020-09-29 06:06:25 +00:00
|
|
|
.filter(IdentityRecord.Columns.id == id)
|
|
|
|
.updateAll($0, IdentityRecord.Columns.preferences.set(to: data))
|
2020-09-07 16:33:36 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|