mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 17:50:59 +00:00
Adjust schema to accomodate moved account property
This commit is contained in:
parent
4b0f896f64
commit
81169ffd2f
10 changed files with 258 additions and 37 deletions
17
DB/Sources/DB/Content/AccountResult.swift
Normal file
17
DB/Sources/DB/Content/AccountResult.swift
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
struct AccountResult: Codable, Hashable, FetchableRecord {
|
||||||
|
let account: StoredAccount
|
||||||
|
let moved: StoredAccount?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension QueryInterfaceRequest where RowDecoder == StoredAccount {
|
||||||
|
var accountResultRequest: AnyFetchRequest<AccountResult> {
|
||||||
|
AnyFetchRequest(including(optional: StoredAccount.moved))
|
||||||
|
.asRequest(of: AccountResult.self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -193,7 +193,7 @@ private extension ContentDatabase {
|
||||||
var migrator = DatabaseMigrator()
|
var migrator = DatabaseMigrator()
|
||||||
|
|
||||||
migrator.registerMigration("createStatuses") { db in
|
migrator.registerMigration("createStatuses") { db in
|
||||||
try db.create(table: "account", ifNotExists: true) { t in
|
try db.create(table: "storedAccount", ifNotExists: true) { t in
|
||||||
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
||||||
t.column("username", .text).notNull()
|
t.column("username", .text).notNull()
|
||||||
t.column("acct", .text).notNull()
|
t.column("acct", .text).notNull()
|
||||||
|
@ -213,13 +213,14 @@ private extension ContentDatabase {
|
||||||
t.column("emojis", .blob).notNull()
|
t.column("emojis", .blob).notNull()
|
||||||
t.column("bot", .boolean).notNull()
|
t.column("bot", .boolean).notNull()
|
||||||
t.column("discoverable", .boolean)
|
t.column("discoverable", .boolean)
|
||||||
|
t.column("movedId", .text).indexed().references("storedAccount", column: "id")
|
||||||
}
|
}
|
||||||
|
|
||||||
try db.create(table: "storedStatus", ifNotExists: true) { t in
|
try db.create(table: "storedStatus", ifNotExists: true) { t in
|
||||||
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
||||||
t.column("uri", .text).notNull()
|
t.column("uri", .text).notNull()
|
||||||
t.column("createdAt", .datetime).notNull()
|
t.column("createdAt", .datetime).notNull()
|
||||||
t.column("accountId", .text).indexed().notNull().references("account", column: "id")
|
t.column("accountId", .text).indexed().notNull().references("storedAccount", column: "id")
|
||||||
t.column("content", .text).notNull()
|
t.column("content", .text).notNull()
|
||||||
t.column("visibility", .text).notNull()
|
t.column("visibility", .text).notNull()
|
||||||
t.column("sensitive", .boolean).notNull()
|
t.column("sensitive", .boolean).notNull()
|
||||||
|
|
|
@ -5,17 +5,33 @@ import GRDB
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
|
||||||
struct StatusResult: Codable, Hashable, FetchableRecord {
|
struct StatusResult: Codable, Hashable, FetchableRecord {
|
||||||
let account: Account
|
let account: StoredAccount
|
||||||
|
let accountMoved: StoredAccount?
|
||||||
let status: StoredStatus
|
let status: StoredStatus
|
||||||
let reblogAccount: Account?
|
let reblogAccount: StoredAccount?
|
||||||
|
let reblogAccountMoved: StoredAccount?
|
||||||
let reblog: StoredStatus?
|
let reblog: StoredStatus?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension StatusResult {
|
||||||
|
var accountResult: AccountResult {
|
||||||
|
AccountResult(account: account, moved: accountMoved)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reblogAccountResult: AccountResult? {
|
||||||
|
guard let reblogAccount = reblogAccount else { return nil }
|
||||||
|
|
||||||
|
return AccountResult(account: reblogAccount, moved: reblogAccountMoved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension QueryInterfaceRequest where RowDecoder == StoredStatus {
|
extension QueryInterfaceRequest where RowDecoder == StoredStatus {
|
||||||
var statusResultRequest: QueryInterfaceRequest<StatusResult> {
|
var statusResultRequest: AnyFetchRequest<StatusResult> {
|
||||||
including(required: StoredStatus.account)
|
AnyFetchRequest(including(required: StoredStatus.account)
|
||||||
|
.including(optional: StoredStatus.accountMoved)
|
||||||
.including(optional: StoredStatus.reblogAccount)
|
.including(optional: StoredStatus.reblogAccount)
|
||||||
.including(optional: StoredStatus.reblog)
|
.including(optional: StoredStatus.reblogAccountMoved)
|
||||||
|
.including(optional: StoredStatus.reblog))
|
||||||
.asRequest(of: StatusResult.self)
|
.asRequest(of: StatusResult.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
65
DB/Sources/DB/Content/StoredAccount.swift
Normal file
65
DB/Sources/DB/Content/StoredAccount.swift
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import Mastodon
|
||||||
|
|
||||||
|
struct StoredAccount: Codable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let username: String
|
||||||
|
let acct: String
|
||||||
|
let displayName: String
|
||||||
|
let locked: Bool
|
||||||
|
let createdAt: Date
|
||||||
|
let followersCount: Int
|
||||||
|
let followingCount: Int
|
||||||
|
let statusesCount: Int
|
||||||
|
let note: HTML
|
||||||
|
let url: URL
|
||||||
|
let avatar: URL
|
||||||
|
let avatarStatic: URL
|
||||||
|
let header: URL
|
||||||
|
let headerStatic: URL
|
||||||
|
let fields: [Account.Field]
|
||||||
|
let emojis: [Emoji]
|
||||||
|
let bot: Bool
|
||||||
|
let discoverable: Bool
|
||||||
|
let movedId: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StoredAccount: FetchableRecord, PersistableRecord {
|
||||||
|
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
|
||||||
|
MastodonDecoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
|
||||||
|
MastodonEncoder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StoredAccount {
|
||||||
|
static let moved = belongsTo(StoredAccount.self, key: "moved")
|
||||||
|
|
||||||
|
init(account: Account) {
|
||||||
|
id = account.id
|
||||||
|
username = account.username
|
||||||
|
acct = account.acct
|
||||||
|
displayName = account.displayName
|
||||||
|
locked = account.locked
|
||||||
|
createdAt = account.createdAt
|
||||||
|
followersCount = account.followersCount
|
||||||
|
followingCount = account.followingCount
|
||||||
|
statusesCount = account.statusesCount
|
||||||
|
note = account.note
|
||||||
|
url = account.url
|
||||||
|
avatar = account.avatar
|
||||||
|
avatarStatic = account.avatarStatic
|
||||||
|
header = account.header
|
||||||
|
headerStatic = account.headerStatic
|
||||||
|
fields = account.fields
|
||||||
|
emojis = account.emojis
|
||||||
|
bot = account.bot
|
||||||
|
discoverable = account.discoverable
|
||||||
|
movedId = account.moved?.id
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,8 +47,19 @@ extension StoredStatus: FetchableRecord, PersistableRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StoredStatus {
|
extension StoredStatus {
|
||||||
static let account = belongsTo(Account.self, key: "account")
|
static let account = belongsTo(StoredAccount.self, key: "account", using: ForeignKey([Column("accountId")]))
|
||||||
static let reblogAccount = hasOne(Account.self, through: Self.reblog, using: Self.account, key: "reblogAccount")
|
static let accountMoved = hasOne(StoredAccount.self,
|
||||||
|
through: Self.account,
|
||||||
|
using: StoredAccount.moved,
|
||||||
|
key: "accountMoved")
|
||||||
|
static let reblogAccount = hasOne(StoredAccount.self,
|
||||||
|
through: Self.reblog,
|
||||||
|
using: Self.account,
|
||||||
|
key: "reblogAccount")
|
||||||
|
static let reblogAccountMoved = hasOne(StoredAccount.self,
|
||||||
|
through: Self.reblogAccount,
|
||||||
|
using: StoredAccount.moved,
|
||||||
|
key: "reblogAccountMoved")
|
||||||
static let reblog = belongsTo(StoredStatus.self, key: "reblog")
|
static let reblog = belongsTo(StoredStatus.self, key: "reblog")
|
||||||
static let ancestorJoins = hasMany(StatusContextJoin.self, using: ForeignKey([Column("parentID")]))
|
static let ancestorJoins = hasMany(StatusContextJoin.self, using: ForeignKey([Column("parentID")]))
|
||||||
.filter(Column("section") == StatusContextJoin.Section.ancestors.rawValue)
|
.filter(Column("section") == StatusContextJoin.Section.ancestors.rawValue)
|
||||||
|
@ -63,23 +74,11 @@ extension StoredStatus {
|
||||||
through: descendantJoins,
|
through: descendantJoins,
|
||||||
using: StatusContextJoin.status)
|
using: StatusContextJoin.status)
|
||||||
|
|
||||||
var account: QueryInterfaceRequest<Account> {
|
var ancestors: AnyFetchRequest<StatusResult> {
|
||||||
request(for: Self.account)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reblogAccount: QueryInterfaceRequest<Account> {
|
|
||||||
request(for: Self.reblogAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reblog: QueryInterfaceRequest<StoredStatus> {
|
|
||||||
request(for: Self.reblog)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ancestors: QueryInterfaceRequest<StatusResult> {
|
|
||||||
request(for: Self.ancestors).statusResultRequest
|
request(for: Self.ancestors).statusResultRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
var descendants: QueryInterfaceRequest<StatusResult> {
|
var descendants: AnyFetchRequest<StatusResult> {
|
||||||
request(for: Self.descendants).statusResultRequest
|
request(for: Self.descendants).statusResultRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,45 @@ import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
|
||||||
extension Account: FetchableRecord, PersistableRecord {
|
extension Account {
|
||||||
public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
|
func save(_ db: Database) throws {
|
||||||
MastodonDecoder()
|
if let moved = moved {
|
||||||
|
try StoredAccount(account: moved).save(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func databaseJSONEncoder(for column: String) -> JSONEncoder {
|
try StoredAccount(account: self).save(db)
|
||||||
MastodonEncoder()
|
}
|
||||||
|
|
||||||
|
convenience init(accountResult: AccountResult) {
|
||||||
|
var moved: Account?
|
||||||
|
|
||||||
|
if let movedResult = accountResult.moved {
|
||||||
|
moved = Self(storedAccount: movedResult, moved: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(storedAccount: accountResult.account, moved: moved)
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(storedAccount: StoredAccount, moved: Account?) {
|
||||||
|
self.init(id: storedAccount.id,
|
||||||
|
username: storedAccount.username,
|
||||||
|
acct: storedAccount.acct,
|
||||||
|
displayName: storedAccount.displayName,
|
||||||
|
locked: storedAccount.locked,
|
||||||
|
createdAt: storedAccount.createdAt,
|
||||||
|
followersCount: storedAccount.followersCount,
|
||||||
|
followingCount: storedAccount.followingCount,
|
||||||
|
statusesCount: storedAccount.statusesCount,
|
||||||
|
note: storedAccount.note,
|
||||||
|
url: storedAccount.url,
|
||||||
|
avatar: storedAccount.avatar,
|
||||||
|
avatarStatic: storedAccount.avatarStatic,
|
||||||
|
header: storedAccount.header,
|
||||||
|
headerStatic: storedAccount.headerStatic,
|
||||||
|
fields: storedAccount.fields,
|
||||||
|
emojis: storedAccount.emojis,
|
||||||
|
bot: storedAccount.bot,
|
||||||
|
discoverable: storedAccount.discoverable,
|
||||||
|
moved: moved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,11 @@ extension Status {
|
||||||
convenience init(statusResult: StatusResult) {
|
convenience init(statusResult: StatusResult) {
|
||||||
var reblog: Status?
|
var reblog: Status?
|
||||||
|
|
||||||
if let reblogResult = statusResult.reblog, let reblogAccount = statusResult.reblogAccount {
|
if let reblogResult = statusResult.reblog, let reblogAccount = statusResult.reblogAccountResult {
|
||||||
reblog = Status(storedStatus: reblogResult, account: reblogAccount, reblog: nil)
|
reblog = Status(storedStatus: reblogResult, account: Account(accountResult: reblogAccount), reblog: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.init(storedStatus: statusResult.status, account: statusResult.account, reblog: reblog)
|
self.init(storedStatus: statusResult.status, account: Account(accountResult: statusResult.accountResult), reblog: reblog)
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(storedStatus: StoredStatus, account: Account, reblog: Status?) {
|
convenience init(storedStatus: StoredStatus, account: Account, reblog: Status?) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ extension Timeline {
|
||||||
using: TimelineStatusJoin.status)
|
using: TimelineStatusJoin.status)
|
||||||
.order(Column("createdAt").desc)
|
.order(Column("createdAt").desc)
|
||||||
|
|
||||||
var statuses: QueryInterfaceRequest<StatusResult> {
|
var statuses: AnyFetchRequest<StatusResult> {
|
||||||
request(for: Self.statuses).statusResultRequest
|
request(for: Self.statuses).statusResultRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Account: Codable, Hashable {
|
public final class Account: Codable, Identifiable {
|
||||||
public struct Field: Codable, Hashable {
|
public struct Field: Codable, Hashable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let value: HTML
|
public let value: HTML
|
||||||
|
@ -28,4 +28,95 @@ public struct Account: Codable, Hashable {
|
||||||
public let emojis: [Emoji]
|
public let emojis: [Emoji]
|
||||||
@DecodableDefault.False public private(set) var bot: Bool
|
@DecodableDefault.False public private(set) var bot: Bool
|
||||||
@DecodableDefault.False public private(set) var discoverable: Bool
|
@DecodableDefault.False public private(set) var discoverable: Bool
|
||||||
|
public var moved: Account?
|
||||||
|
|
||||||
|
public init(id: String,
|
||||||
|
username: String,
|
||||||
|
acct: String,
|
||||||
|
displayName: String,
|
||||||
|
locked: Bool,
|
||||||
|
createdAt: Date,
|
||||||
|
followersCount: Int,
|
||||||
|
followingCount: Int,
|
||||||
|
statusesCount: Int,
|
||||||
|
note: HTML,
|
||||||
|
url: URL,
|
||||||
|
avatar: URL,
|
||||||
|
avatarStatic: URL,
|
||||||
|
header: URL,
|
||||||
|
headerStatic: URL,
|
||||||
|
fields: [Account.Field],
|
||||||
|
emojis: [Emoji],
|
||||||
|
bot: Bool,
|
||||||
|
discoverable: Bool,
|
||||||
|
moved: Account?) {
|
||||||
|
self.id = id
|
||||||
|
self.username = username
|
||||||
|
self.acct = acct
|
||||||
|
self.displayName = displayName
|
||||||
|
self.locked = locked
|
||||||
|
self.createdAt = createdAt
|
||||||
|
self.followersCount = followersCount
|
||||||
|
self.followingCount = followingCount
|
||||||
|
self.statusesCount = statusesCount
|
||||||
|
self.note = note
|
||||||
|
self.url = url
|
||||||
|
self.avatar = avatar
|
||||||
|
self.avatarStatic = avatarStatic
|
||||||
|
self.header = header
|
||||||
|
self.headerStatic = headerStatic
|
||||||
|
self.fields = fields
|
||||||
|
self.emojis = emojis
|
||||||
|
self.bot = bot
|
||||||
|
self.discoverable = discoverable
|
||||||
|
self.moved = moved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Account: Hashable {
|
||||||
|
public static func == (lhs: Account, rhs: Account) -> Bool {
|
||||||
|
return lhs.id == rhs.id &&
|
||||||
|
lhs.username == rhs.username &&
|
||||||
|
lhs.acct == rhs.acct &&
|
||||||
|
lhs.displayName == rhs.displayName &&
|
||||||
|
lhs.locked == rhs.locked &&
|
||||||
|
lhs.createdAt == rhs.createdAt &&
|
||||||
|
lhs.followersCount == rhs.followersCount &&
|
||||||
|
lhs.followingCount == rhs.followingCount &&
|
||||||
|
lhs.statusesCount == rhs.statusesCount &&
|
||||||
|
lhs.note == rhs.note &&
|
||||||
|
lhs.url == rhs.url &&
|
||||||
|
lhs.avatar == rhs.avatar &&
|
||||||
|
lhs.avatarStatic == rhs.avatarStatic &&
|
||||||
|
lhs.header == rhs.header &&
|
||||||
|
lhs.headerStatic == rhs.headerStatic &&
|
||||||
|
lhs.fields == rhs.fields &&
|
||||||
|
lhs.emojis == rhs.emojis &&
|
||||||
|
lhs._bot == rhs._bot &&
|
||||||
|
lhs._discoverable == rhs._discoverable &&
|
||||||
|
lhs.moved == rhs.moved
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(id)
|
||||||
|
hasher.combine(username)
|
||||||
|
hasher.combine(acct)
|
||||||
|
hasher.combine(displayName)
|
||||||
|
hasher.combine(locked)
|
||||||
|
hasher.combine(createdAt)
|
||||||
|
hasher.combine(followersCount)
|
||||||
|
hasher.combine(followingCount)
|
||||||
|
hasher.combine(statusesCount)
|
||||||
|
hasher.combine(note)
|
||||||
|
hasher.combine(url)
|
||||||
|
hasher.combine(avatar)
|
||||||
|
hasher.combine(avatarStatic)
|
||||||
|
hasher.combine(header)
|
||||||
|
hasher.combine(headerStatic)
|
||||||
|
hasher.combine(fields)
|
||||||
|
hasher.combine(emojis)
|
||||||
|
hasher.combine(bot)
|
||||||
|
hasher.combine(discoverable)
|
||||||
|
hasher.combine(moved)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@ public final class Status: Codable, Identifiable {
|
||||||
@DecodableDefault.False public private(set) var bookmarked: Bool
|
@DecodableDefault.False public private(set) var bookmarked: Bool
|
||||||
public let pinned: Bool?
|
public let pinned: Bool?
|
||||||
|
|
||||||
// Xcode-generated memberwise initializer
|
|
||||||
public init(
|
public init(
|
||||||
id: String,
|
id: String,
|
||||||
uri: String,
|
uri: String,
|
||||||
|
|
Loading…
Reference in a new issue