2020-08-18 05:13:37 +00:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Combine
|
2020-09-05 02:31:43 +00:00
|
|
|
import Foundation
|
2020-08-18 05:13:37 +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-08-18 05:13:37 +00:00
|
|
|
|
2020-09-28 03:11:49 +00:00
|
|
|
// swiftlint:disable file_length
|
2020-09-03 03:28:34 +00:00
|
|
|
public struct ContentDatabase {
|
2020-09-28 03:11:49 +00:00
|
|
|
private let databaseWriter: DatabaseWriter
|
2020-08-18 05:13:37 +00:00
|
|
|
|
2020-09-04 06:12:06 +00:00
|
|
|
public init(identityID: UUID, inMemory: Bool, keychain: Keychain.Type) throws {
|
2020-09-03 03:28:34 +00:00
|
|
|
if inMemory {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter = DatabaseQueue()
|
2020-08-18 05:13:37 +00:00
|
|
|
} else {
|
2020-09-04 06:12:06 +00:00
|
|
|
let path = try Self.fileURL(identityID: identityID).path
|
|
|
|
var configuration = Configuration()
|
|
|
|
|
2020-09-07 14:25:26 +00:00
|
|
|
configuration.prepareDatabase {
|
2020-09-28 03:11:49 +00:00
|
|
|
try $0.usePassphrase(Secrets.databaseKey(identityID: identityID, keychain: keychain))
|
2020-09-04 06:12:06 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter = try DatabasePool(path: path, configuration: configuration)
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 06:05:08 +00:00
|
|
|
try migrator.migrate(databaseWriter)
|
2020-09-28 03:11:49 +00:00
|
|
|
clean()
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 03:28:34 +00:00
|
|
|
public extension ContentDatabase {
|
2020-09-03 01:14:33 +00:00
|
|
|
static func delete(forIdentityID identityID: UUID) throws {
|
2020-09-15 23:58:04 +00:00
|
|
|
try FileManager.default.removeItem(at: fileURL(identityID: identityID))
|
2020-09-03 01:14:33 +00:00
|
|
|
}
|
|
|
|
|
2020-09-02 09:39:44 +00:00
|
|
|
func insert(status: Status) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher(updates: status.save)
|
2020-09-02 09:39:44 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func insert(statuses: [Status], timeline: Timeline) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-02 09:39:44 +00:00
|
|
|
try timeline.save($0)
|
2020-08-18 05:13:37 +00:00
|
|
|
|
|
|
|
for status in statuses {
|
2020-09-02 09:39:44 +00:00
|
|
|
try status.save($0)
|
2020-08-18 05:13:37 +00:00
|
|
|
|
2020-09-02 09:39:44 +00:00
|
|
|
try TimelineStatusJoin(timelineId: timeline.id, statusId: status.id).save($0)
|
2020-09-02 02:39:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func insert(context: Context, parentID: String) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-02 02:39:06 +00:00
|
|
|
for status in context.ancestors + context.descendants {
|
2020-09-02 09:39:44 +00:00
|
|
|
try status.save($0)
|
2020-09-02 02:39:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (section, statuses) in [(StatusContextJoin.Section.ancestors, context.ancestors),
|
|
|
|
(StatusContextJoin.Section.descendants, context.descendants)] {
|
|
|
|
for (index, status) in statuses.enumerated() {
|
|
|
|
try StatusContextJoin(
|
|
|
|
parentId: parentID,
|
|
|
|
statusId: status.id,
|
|
|
|
section: section,
|
|
|
|
index: index)
|
|
|
|
.save($0)
|
|
|
|
}
|
|
|
|
|
|
|
|
try StatusContextJoin.filter(
|
|
|
|
Column("parentId") == parentID
|
|
|
|
&& Column("section") == section.rawValue
|
2020-09-17 03:16:32 +00:00
|
|
|
&& !statuses.map(\.id).contains(Column("statusId")))
|
2020-09-02 02:39:06 +00:00
|
|
|
.deleteAll($0)
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-26 09:19:38 +00:00
|
|
|
.ignoreOutput()
|
2020-08-18 05:13:37 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-09-18 00:16:41 +00:00
|
|
|
func insert(pinnedStatuses: [Status], accountID: String) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-18 00:16:41 +00:00
|
|
|
for (index, status) in pinnedStatuses.enumerated() {
|
|
|
|
try status.save($0)
|
|
|
|
|
|
|
|
try AccountPinnedStatusJoin(accountId: accountID, statusId: status.id, index: index).save($0)
|
|
|
|
}
|
|
|
|
|
|
|
|
try AccountPinnedStatusJoin.filter(
|
|
|
|
Column("accountId") == accountID
|
|
|
|
&& !pinnedStatuses.map(\.id).contains(Column("statusId")))
|
|
|
|
.deleteAll($0)
|
|
|
|
}
|
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func insert(
|
|
|
|
statuses: [Status],
|
|
|
|
accountID: String,
|
2020-09-27 02:03:53 +00:00
|
|
|
collection: ProfileCollection) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-18 00:16:41 +00:00
|
|
|
for status in statuses {
|
|
|
|
try status.save($0)
|
|
|
|
|
|
|
|
try AccountStatusJoin(accountId: accountID, statusId: status.id, collection: collection).save($0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-09-28 22:40:03 +00:00
|
|
|
func append(accounts: [Account], toList list: AccountList) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-09-28 22:40:03 +00:00
|
|
|
try list.save($0)
|
|
|
|
|
|
|
|
let count = try list.accounts.fetchCount($0)
|
|
|
|
|
|
|
|
for (index, account) in accounts.enumerated() {
|
2020-09-22 06:53:11 +00:00
|
|
|
try account.save($0)
|
2020-09-28 22:40:03 +00:00
|
|
|
try AccountListJoin(accountId: account.id, listId: list.id, index: count + index).save($0)
|
2020-09-22 06:53:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-09-26 03:28:50 +00:00
|
|
|
func setLists(_ lists: [List]) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-08-29 03:50:58 +00:00
|
|
|
for list in lists {
|
|
|
|
try Timeline.list(list).save($0)
|
|
|
|
}
|
|
|
|
|
2020-09-09 05:40:49 +00:00
|
|
|
try Timeline
|
2020-09-15 06:40:02 +00:00
|
|
|
.filter(!(Timeline.authenticatedDefaults.map(\.id) + lists.map(\.id)).contains(Column("id"))
|
|
|
|
&& Column("listTitle") != nil)
|
2020-09-09 05:40:49 +00:00
|
|
|
.deleteAll($0)
|
2020-08-29 03:50:58 +00:00
|
|
|
}
|
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-09-26 03:28:50 +00:00
|
|
|
func createList(_ list: List) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher(updates: Timeline.list(list).save)
|
2020-08-29 03:50:58 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func deleteList(id: String) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher(updates: Timeline.filter(Column("id") == id).deleteAll)
|
2020-08-29 10:26:26 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func setFilters(_ filters: [Filter]) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher {
|
2020-08-29 10:26:26 +00:00
|
|
|
for filter in filters {
|
|
|
|
try filter.save($0)
|
|
|
|
}
|
|
|
|
|
|
|
|
try Filter.filter(!filters.map(\.id).contains(Column("id"))).deleteAll($0)
|
|
|
|
}
|
2020-08-29 03:50:58 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-08-29 10:26:26 +00:00
|
|
|
func createFilter(_ filter: Filter) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher(updates: filter.save)
|
2020-08-29 10:26:26 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func deleteFilter(id: String) -> AnyPublisher<Never, Error> {
|
2020-09-28 03:11:49 +00:00
|
|
|
databaseWriter.writePublisher(updates: Filter.filter(Column("id") == id).deleteAll)
|
2020-08-29 10:26:26 +00:00
|
|
|
.ignoreOutput()
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-09-02 02:39:06 +00:00
|
|
|
func statusesObservation(timeline: Timeline) -> AnyPublisher<[[Status]], Error> {
|
|
|
|
ValueObservation.tracking(timeline.statuses.fetchAll)
|
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-09-05 02:05:15 +00:00
|
|
|
.map { [$0.map(Status.init(result:))] }
|
2020-09-02 02:39:06 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func contextObservation(parentID: String) -> AnyPublisher<[[Status]], Error> {
|
|
|
|
ValueObservation.tracking { db -> [[StatusResult]] in
|
2020-09-05 02:05:15 +00:00
|
|
|
guard let parent = try StatusRecord.filter(Column("id") == parentID).statusResultRequest.fetchOne(db) else {
|
2020-09-02 02:39:06 +00:00
|
|
|
return [[]]
|
|
|
|
}
|
|
|
|
|
|
|
|
let ancestors = try parent.status.ancestors.fetchAll(db)
|
|
|
|
let descendants = try parent.status.descendants.fetchAll(db)
|
|
|
|
|
|
|
|
return [ancestors, [parent], descendants]
|
|
|
|
}
|
2020-08-19 22:16:03 +00:00
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-09-05 02:05:15 +00:00
|
|
|
.map { $0.map { $0.map(Status.init(result:)) } }
|
2020-08-19 22:16:03 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
2020-08-29 03:50:58 +00:00
|
|
|
|
2020-09-18 00:16:41 +00:00
|
|
|
func statusesObservation(
|
|
|
|
accountID: String,
|
2020-09-27 02:03:53 +00:00
|
|
|
collection: ProfileCollection) -> AnyPublisher<[[Status]], Error> {
|
2020-09-18 00:16:41 +00:00
|
|
|
ValueObservation.tracking { db -> [[StatusResult]] in
|
|
|
|
let statuses = try StatusRecord.filter(
|
|
|
|
AccountStatusJoin
|
|
|
|
.select(Column("statusId"), as: String.self)
|
|
|
|
.filter(sql: "accountId = ? AND collection = ?", arguments: [accountID, collection.rawValue])
|
|
|
|
.contains(Column("id")))
|
|
|
|
.order(Column("createdAt").desc)
|
|
|
|
.statusResultRequest
|
|
|
|
.fetchAll(db)
|
|
|
|
|
|
|
|
if
|
|
|
|
case .statuses = collection,
|
|
|
|
let accountRecord = try AccountRecord.filter(Column("id") == accountID).fetchOne(db) {
|
|
|
|
let pinnedStatuses = try accountRecord.pinnedStatuses.fetchAll(db)
|
|
|
|
|
|
|
|
return [pinnedStatuses, statuses]
|
|
|
|
} else {
|
|
|
|
return [statuses]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-09-18 00:16:41 +00:00
|
|
|
.map { $0.map { $0.map(Status.init(result:)) } }
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-08-29 03:50:58 +00:00
|
|
|
func listsObservation() -> AnyPublisher<[Timeline], Error> {
|
2020-09-16 06:37:24 +00:00
|
|
|
ValueObservation.tracking(Timeline.filter(Column("listTitle") != nil)
|
2020-09-28 05:29:24 +00:00
|
|
|
.order(Column("listTitle").asc)
|
2020-08-29 03:50:58 +00:00
|
|
|
.fetchAll)
|
2020-08-29 10:26:26 +00:00
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-08-29 10:26:26 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
2020-08-30 05:31:30 +00:00
|
|
|
func activeFiltersObservation(date: Date, context: Filter.Context? = nil) -> AnyPublisher<[Filter], Error> {
|
2020-08-30 00:32:34 +00:00
|
|
|
ValueObservation.tracking(Filter.filter(Column("expiresAt") == nil || Column("expiresAt") > date).fetchAll)
|
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-08-30 05:31:30 +00:00
|
|
|
.map {
|
|
|
|
guard let context = context else { return $0 }
|
|
|
|
|
|
|
|
return $0.filter { $0.context.contains(context) }
|
|
|
|
}
|
2020-08-30 00:32:34 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
func expiredFiltersObservation(date: Date) -> AnyPublisher<[Filter], Error> {
|
|
|
|
ValueObservation.tracking(Filter.filter(Column("expiresAt") < date).fetchAll)
|
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-08-30 00:32:34 +00:00
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
2020-09-22 06:53:11 +00:00
|
|
|
|
|
|
|
func accountObservation(id: String) -> AnyPublisher<Account?, Error> {
|
|
|
|
ValueObservation.tracking(AccountRecord.filter(Column("id") == id).accountResultRequest.fetchOne)
|
|
|
|
.removeDuplicates()
|
2020-09-28 03:11:49 +00:00
|
|
|
.publisher(in: databaseWriter)
|
2020-09-22 06:53:11 +00:00
|
|
|
.map {
|
|
|
|
if let result = $0 {
|
|
|
|
return Account(result: result)
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
2020-09-28 22:40:03 +00:00
|
|
|
|
|
|
|
func accountListObservation(_ list: AccountList) -> AnyPublisher<[Account], Error> {
|
|
|
|
ValueObservation.tracking(list.accounts.fetchAll)
|
|
|
|
.removeDuplicates()
|
|
|
|
.publisher(in: databaseWriter)
|
|
|
|
.map { $0.map(Account.init(result:)) }
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private extension ContentDatabase {
|
2020-09-03 01:14:33 +00:00
|
|
|
static func fileURL(identityID: UUID) throws -> URL {
|
2020-09-04 06:12:06 +00:00
|
|
|
try FileManager.default.databaseDirectoryURL(name: identityID.uuidString)
|
2020-09-03 01:14:33 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 06:05:08 +00:00
|
|
|
private var migrator: DatabaseMigrator {
|
2020-08-18 05:13:37 +00:00
|
|
|
var migrator = DatabaseMigrator()
|
|
|
|
|
2020-09-05 05:52:16 +00:00
|
|
|
migrator.registerMigration("0.1.0") { db in
|
|
|
|
try db.create(table: "accountRecord") { t in
|
2020-09-28 09:14:34 +00:00
|
|
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
2020-08-18 05:13:37 +00:00
|
|
|
t.column("username", .text).notNull()
|
|
|
|
t.column("acct", .text).notNull()
|
|
|
|
t.column("displayName", .text).notNull()
|
|
|
|
t.column("locked", .boolean).notNull()
|
|
|
|
t.column("createdAt", .date).notNull()
|
|
|
|
t.column("followersCount", .integer).notNull()
|
|
|
|
t.column("followingCount", .integer).notNull()
|
|
|
|
t.column("statusesCount", .integer).notNull()
|
|
|
|
t.column("note", .text).notNull()
|
|
|
|
t.column("url", .text).notNull()
|
|
|
|
t.column("avatar", .text).notNull()
|
|
|
|
t.column("avatarStatic", .text).notNull()
|
|
|
|
t.column("header", .text).notNull()
|
|
|
|
t.column("headerStatic", .text).notNull()
|
|
|
|
t.column("fields", .blob).notNull()
|
|
|
|
t.column("emojis", .blob).notNull()
|
|
|
|
t.column("bot", .boolean).notNull()
|
|
|
|
t.column("discoverable", .boolean)
|
2020-09-28 03:11:49 +00:00
|
|
|
t.column("movedId", .text).references("accountRecord", column: "id")
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
|
|
|
|
2020-09-05 05:52:16 +00:00
|
|
|
try db.create(table: "statusRecord") { t in
|
2020-09-28 09:14:34 +00:00
|
|
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
2020-08-18 05:13:37 +00:00
|
|
|
t.column("uri", .text).notNull()
|
|
|
|
t.column("createdAt", .datetime).notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
t.column("accountId", .text).notNull().references("accountRecord", column: "id")
|
2020-08-18 05:13:37 +00:00
|
|
|
t.column("content", .text).notNull()
|
|
|
|
t.column("visibility", .text).notNull()
|
|
|
|
t.column("sensitive", .boolean).notNull()
|
|
|
|
t.column("spoilerText", .text).notNull()
|
|
|
|
t.column("mediaAttachments", .blob).notNull()
|
|
|
|
t.column("mentions", .blob).notNull()
|
|
|
|
t.column("tags", .blob).notNull()
|
|
|
|
t.column("emojis", .blob).notNull()
|
|
|
|
t.column("reblogsCount", .integer).notNull()
|
|
|
|
t.column("favouritesCount", .integer).notNull()
|
|
|
|
t.column("repliesCount", .integer).notNull()
|
|
|
|
t.column("application", .blob)
|
|
|
|
t.column("url", .text)
|
|
|
|
t.column("inReplyToId", .text)
|
|
|
|
t.column("inReplyToAccountId", .text)
|
2020-09-28 03:11:49 +00:00
|
|
|
t.column("reblogId", .text).references("statusRecord", column: "id")
|
2020-08-18 05:13:37 +00:00
|
|
|
t.column("poll", .blob)
|
|
|
|
t.column("card", .blob)
|
|
|
|
t.column("language", .text)
|
|
|
|
t.column("text", .text)
|
2020-08-23 23:39:52 +00:00
|
|
|
t.column("favourited", .boolean).notNull()
|
|
|
|
t.column("reblogged", .boolean).notNull()
|
|
|
|
t.column("muted", .boolean).notNull()
|
|
|
|
t.column("bookmarked", .boolean).notNull()
|
2020-08-18 05:13:37 +00:00
|
|
|
t.column("pinned", .boolean)
|
|
|
|
}
|
|
|
|
|
2020-09-05 05:52:16 +00:00
|
|
|
try db.create(table: "timeline") { t in
|
2020-09-28 09:14:34 +00:00
|
|
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
2020-09-28 05:29:24 +00:00
|
|
|
t.column("listTitle", .text).indexed().collate(.localizedCaseInsensitiveCompare)
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
|
|
|
|
2020-09-05 05:52:16 +00:00
|
|
|
try db.create(table: "timelineStatusJoin") { t in
|
2020-09-28 03:11:49 +00:00
|
|
|
t.column("timelineId", .text).indexed().notNull()
|
2020-08-18 05:13:37 +00:00
|
|
|
.references("timeline", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
2020-09-28 03:11:49 +00:00
|
|
|
t.column("statusId", .text).indexed().notNull()
|
2020-09-05 02:05:15 +00:00
|
|
|
.references("statusRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
2020-08-18 05:13:37 +00:00
|
|
|
|
|
|
|
t.primaryKey(["timelineId", "statusId"], onConflict: .replace)
|
|
|
|
}
|
2020-08-29 10:26:26 +00:00
|
|
|
|
2020-09-05 05:52:16 +00:00
|
|
|
try db.create(table: "filter") { t in
|
2020-09-28 09:14:34 +00:00
|
|
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
2020-08-29 10:26:26 +00:00
|
|
|
t.column("phrase", .text).notNull()
|
|
|
|
t.column("context", .blob).notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
t.column("expiresAt", .date).indexed()
|
2020-08-29 10:26:26 +00:00
|
|
|
t.column("irreversible", .boolean).notNull()
|
|
|
|
t.column("wholeWord", .boolean).notNull()
|
|
|
|
}
|
2020-09-17 03:16:32 +00:00
|
|
|
|
2020-09-28 03:11:49 +00:00
|
|
|
try db.create(table: "statusContextJoin") { t in
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("parentId", .text).indexed().notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
.references("statusRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("statusId", .text).indexed().notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
.references("statusRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
|
|
|
t.column("section", .text).indexed().notNull()
|
2020-09-17 03:16:32 +00:00
|
|
|
t.column("index", .integer).notNull()
|
|
|
|
|
|
|
|
t.primaryKey(["parentId", "statusId"], onConflict: .replace)
|
|
|
|
}
|
2020-09-18 00:16:41 +00:00
|
|
|
|
2020-09-28 03:11:49 +00:00
|
|
|
try db.create(table: "accountPinnedStatusJoin") { t in
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("accountId", .text).indexed().notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
.references("accountRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("statusId", .text).indexed().notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
.references("statusRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("index", .integer).notNull()
|
|
|
|
|
|
|
|
t.primaryKey(["accountId", "statusId"], onConflict: .replace)
|
|
|
|
}
|
|
|
|
|
2020-09-28 03:11:49 +00:00
|
|
|
try db.create(table: "accountStatusJoin") { t in
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("accountId", .text).indexed().notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
.references("accountRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
2020-09-18 00:16:41 +00:00
|
|
|
t.column("statusId", .text).indexed().notNull()
|
2020-09-28 03:11:49 +00:00
|
|
|
.references("statusRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
|
|
|
t.column("collection", .text).indexed().notNull()
|
2020-09-18 00:16:41 +00:00
|
|
|
|
|
|
|
t.primaryKey(["accountId", "statusId", "collection"], onConflict: .replace)
|
|
|
|
}
|
2020-09-28 22:40:03 +00:00
|
|
|
|
|
|
|
try db.create(table: "accountList") { t in
|
|
|
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
|
|
|
}
|
|
|
|
|
|
|
|
try db.create(table: "accountListJoin") { t in
|
|
|
|
t.column("accountId", .text).indexed().notNull()
|
|
|
|
.references("accountRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
|
|
|
t.column("listId", .text).indexed().notNull()
|
|
|
|
.references("accountList", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
|
|
|
t.column("index", .integer).notNull()
|
|
|
|
|
|
|
|
t.primaryKey(["accountId", "listId"], onConflict: .replace)
|
|
|
|
}
|
2020-09-17 03:16:32 +00:00
|
|
|
}
|
2020-09-28 03:11:49 +00:00
|
|
|
|
2020-09-28 06:05:08 +00:00
|
|
|
return migrator
|
2020-09-28 03:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func clean() {
|
|
|
|
databaseWriter.asyncWrite {
|
2020-09-28 19:36:19 +00:00
|
|
|
try TimelineStatusJoin.filter(Column("timelineId") != Timeline.home.id).deleteAll($0)
|
|
|
|
try StatusContextJoin.deleteAll($0)
|
|
|
|
try AccountPinnedStatusJoin.deleteAll($0)
|
|
|
|
try AccountStatusJoin.deleteAll($0)
|
2020-09-28 22:40:03 +00:00
|
|
|
try AccountList.deleteAll($0)
|
2020-09-28 03:11:49 +00:00
|
|
|
} completion: { _, _ in }
|
2020-09-17 03:16:32 +00:00
|
|
|
}
|
2020-08-18 05:13:37 +00:00
|
|
|
}
|
2020-09-28 03:11:49 +00:00
|
|
|
// swiftlint:enable file_length
|