metatext/DB/Sources/DB/Identity/IdentityDatabase.swift

227 lines
9 KiB
Swift
Raw Normal View History

// Copyright © 2020 Metabolist. All rights reserved.
import Combine
2020-09-05 02:31:43 +00:00
import Foundation
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-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-11-09 03:07:23 +00:00
public init(inMemory: Bool, appGroup: String, 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-11-09 03:07:23 +00:00
try Self.migrator.migrate(databaseWriter)
} else {
2021-01-16 21:46:07 +00:00
let url = try FileManager.default.databaseDirectoryURL(
name: Secrets.identityDatabaseName(keychain: keychain),
appGroup: appGroup)
2020-09-03 01:14:33 +00:00
2020-11-09 03:07:23 +00:00
databaseWriter = try DatabasePool.withFileCoordinator(url: url, migrator: Self.migrator) {
try Secrets.databaseKey(identityId: nil, keychain: keychain)
2020-09-04 06:12:06 +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> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher(
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-10-05 22:50:05 +00:00
func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher(updates: IdentityRecord.filter(IdentityRecord.Columns.id == id).deleteAll)
2020-08-08 07:43:06 +00:00
}
2020-10-05 22:50:05 +00:00
func updateLastUsedAt(id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher {
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-10-05 22:50:05 +00:00
func updateInstance(_ instance: Instance, id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher {
try Identity.Instance(
uri: instance.uri,
streamingAPI: instance.urls.streamingApi,
title: instance.title,
thumbnail: instance.thumbnail,
version: instance.version,
maxTootChars: instance.maxTootChars)
.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-10-05 22:50:05 +00:00
func updateAccount(_ account: Account, id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher(
2020-08-02 07:02:03 +00:00
updates: Identity.Account(
id: account.id,
2020-10-05 22:50:05 +00:00
identityId: id,
username: account.username,
2020-08-08 09:10:05 +00:00
displayName: account.displayName,
url: account.url,
avatar: account.avatar,
avatarStatic: account.avatarStatic,
header: account.header,
2020-08-08 09:10:05 +00:00
headerStatic: account.headerStatic,
2021-01-26 06:57:44 +00:00
emojis: account.emojis,
followRequestCount: account.source?.followRequestsCount ?? 0)
2020-08-02 07:02:03 +00:00
.save)
2020-08-07 10:14:14 +00:00
}
2020-10-05 22:50:05 +00:00
func confirmIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher {
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
}
}
2020-10-05 22:50:05 +00:00
func updatePreferences(_ preferences: Mastodon.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher {
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-10-05 22:50:05 +00:00
func updatePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher(updates: Self.writePreferences(preferences, id: id))
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> {
2021-03-12 23:25:16 +00:00
databaseWriter.mutatingPublisher {
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-10-06 20:44:22 +00:00
func identityPublisher(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-08-04 20:26:09 +00:00
2020-10-06 20:44:22 +00:00
func identitiesPublisher() -> 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-06 20:44:22 +00:00
func recentIdentitiesPublisher(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-12-10 02:44:06 +00:00
func authenticatedIdentitiesPublisher() -> AnyPublisher<[Identity], Error> {
ValueObservation.tracking(
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
.filter(IdentityRecord.Columns.authenticated == true && IdentityRecord.Columns.pending == false)
.fetchAll)
.removeDuplicates()
.publisher(in: databaseWriter)
.map { $0.map(Identity.init(info:)) }
.eraseToAnyPublisher()
}
2020-10-06 20:44:22 +00:00
func immediateMostRecentlyUsedIdentityIdPublisher() -> 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-10-06 20:44:22 +00:00
func fetchIdentitiesWithOutdatedDeviceTokens(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))
2021-01-29 04:59:06 +00:00
.filter(IdentityRecord.Columns.authenticated == true
&& IdentityRecord.Columns.pending == false
&& 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-12-10 02:44:06 +00:00
func mostRecentAuthenticatedIdentity() throws -> Identity? {
guard let info = try databaseWriter.read(
IdentityInfo.request(IdentityRecord.order(IdentityRecord.Columns.lastUsedAt.desc))
.filter(IdentityRecord.Columns.authenticated == true
&& IdentityRecord.Columns.pending == false)
.fetchOne)
else { return nil }
return Identity(info: info)
}
}
private extension IdentityDatabase {
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
}
}
}