From 81ea2862545623d1fcfdcb65ef4383fcf8470a37 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Wed, 18 Aug 2021 12:08:24 +0200 Subject: [PATCH] more updates --- internal/cliactions/server/server.go | 2 + internal/db/account.go | 19 ++- internal/db/error.go | 7 +- internal/db/pg/account.go | 62 +++----- internal/db/pg/pg.go | 11 ++ internal/db/pg/status.go | 142 ++++++++++-------- internal/db/pg/status_test.go | 2 + internal/db/pg/util.go | 25 +++ internal/db/status.go | 47 +++--- internal/federation/dereferencing/account.go | 5 +- internal/federation/dereferencing/status.go | 11 +- internal/federation/federatingdb/create.go | 2 +- internal/federation/federatingdb/followers.go | 3 +- internal/federation/federatingdb/get.go | 4 +- internal/federation/federatingdb/util.go | 4 +- internal/federation/federatingprotocol.go | 4 +- internal/gtsmodel/status.go | 16 +- internal/gtsmodel/statusfave.go | 2 +- internal/gtsmodel/tag.go | 2 +- internal/processing/federation.go | 2 +- internal/processing/fromfederator.go | 2 +- internal/processing/status/boost.go | 2 +- internal/processing/status/boostedby.go | 9 +- internal/processing/status/context.go | 4 +- internal/processing/status/create.go | 18 +-- internal/processing/status/favedby.go | 9 +- internal/typeutils/astointernal.go | 93 ++++++------ internal/typeutils/internaltofrontend.go | 20 +-- internal/typeutils/util.go | 8 +- testrig/db.go | 4 +- 30 files changed, 290 insertions(+), 251 deletions(-) create mode 100644 internal/db/pg/util.go diff --git a/internal/cliactions/server/server.go b/internal/cliactions/server/server.go index 2314e2608..72c6cfadf 100644 --- a/internal/cliactions/server/server.go +++ b/internal/cliactions/server/server.go @@ -62,6 +62,8 @@ var models []interface{} = []interface{}{ >smodel.MediaAttachment{}, >smodel.Mention{}, >smodel.Status{}, + >smodel.StatusToEmoji{}, + >smodel.StatusToTag{}, >smodel.StatusFave{}, >smodel.StatusBookmark{}, >smodel.StatusMute{}, diff --git a/internal/db/account.go b/internal/db/account.go index d30657cf9..2aa12af57 100644 --- a/internal/db/account.go +++ b/internal/db/account.go @@ -18,14 +18,13 @@ package db -import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +import ( + "time" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) type Account interface { - // GetAccountByUserID is a shortcut for the common action of fetching an account corresponding to a user ID. - // The given account pointer will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetAccountByUserID(userID string, account *gtsmodel.Account) DBError - // GetAccountByID returns one account with the given ID, or an error if something goes wrong. GetAccountByID(id string) (*gtsmodel.Account, DBError) @@ -75,10 +74,10 @@ type Account interface { GetAccountBlocks(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, DBError) - // GetAccountLastStatus simply gets the most recent status by the given account. - // The given slice 'status' pointer will be set to the result of the query, whatever it is. - // In case of no entries, a 'no entries' error will be returned - GetAccountLastStatus(accountID string, status *gtsmodel.Status) DBError + // GetAccountLastPosted simply gets the timestamp of the most recent post by the account. + // + // The returned time will be zero if account has never posted anything. + GetAccountLastPosted(accountID string) (time.Time, DBError) // SetAccountHeaderOrAvatar sets the header or avatar for the given accountID to the given media attachment. SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAttachment, accountID string) DBError diff --git a/internal/db/error.go b/internal/db/error.go index d669f2513..9ccc37b3e 100644 --- a/internal/db/error.go +++ b/internal/db/error.go @@ -23,7 +23,8 @@ import "fmt" type DBError error var ( - ErrNoEntries DBError = fmt.Errorf("no entries") - ErrAlreadyExists DBError = fmt.Errorf("already exists") - ErrUnknown DBError = fmt.Errorf("unknown error") + ErrNoEntries DBError = fmt.Errorf("no entries") + ErrMultipleEntries DBError = fmt.Errorf("multiple entries") + ErrAlreadyExists DBError = fmt.Errorf("already exists") + ErrUnknown DBError = fmt.Errorf("unknown error") ) diff --git a/internal/db/pg/account.go b/internal/db/pg/account.go index 9c1493fdd..6ebfd4de4 100644 --- a/internal/db/pg/account.go +++ b/internal/db/pg/account.go @@ -22,6 +22,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10/orm" @@ -44,24 +45,15 @@ func (a *accountDB) newAccountQ(account *gtsmodel.Account) *orm.Query { Relation("HeaderMediaAttachment") } -func (a *accountDB) processResponse(account *gtsmodel.Account, err error) (*gtsmodel.Account, db.DBError) { - switch err { - case pg.ErrNoRows: - return nil, db.ErrNoEntries - case nil: - return account, nil - default: - return nil, err - } -} - func (a *accountDB) GetAccountByID(id string) (*gtsmodel.Account, db.DBError) { account := >smodel.Account{} q := a.newAccountQ(account). Where("account.id = ?", id) - return a.processResponse(account, q.Select()) + err := processErrorResponse(q.Select()) + + return account, err } func (a *accountDB) GetAccountByURI(uri string) (*gtsmodel.Account, db.DBError) { @@ -70,7 +62,9 @@ func (a *accountDB) GetAccountByURI(uri string) (*gtsmodel.Account, db.DBError) q := a.newAccountQ(account). Where("account.uri = ?", uri) - return a.processResponse(account, q.Select()) + err := processErrorResponse(q.Select()) + + return account, err } func (a *accountDB) GetInstanceAccount(domain string) (*gtsmodel.Account, db.DBError) { @@ -88,18 +82,23 @@ func (a *accountDB) GetInstanceAccount(domain string) (*gtsmodel.Account, db.DBE Where("? IS NULL", pg.Ident("domain")) } - return a.processResponse(account, q.Select()) + err := processErrorResponse(q.Select()) + + return account, err } -func (a *accountDB) GetAccountLastStatus(accountID string, status *gtsmodel.Status) db.DBError { - if err := a.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries - } - return err - } - return nil +func (a *accountDB) GetAccountLastPosted(accountID string) (time.Time, db.DBError) { + status := >smodel.Status{} + q := a.conn.Model(status). + Order("id DESC"). + Limit(1). + Where("account_id = ?", accountID). + Column("created_at") + + err := processErrorResponse(q.Select()) + + return status.CreatedAt, err } func (a *accountDB) SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAttachment, accountID string) db.DBError { @@ -127,25 +126,6 @@ func (a *accountDB) SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAtta return nil } -func (a *accountDB) GetAccountByUserID(userID string, account *gtsmodel.Account) db.DBError { - user := >smodel.User{ - ID: userID, - } - if err := a.conn.Model(user).Where("id = ?", userID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries - } - return err - } - if err := a.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil { - if err == pg.ErrNoRows { - return db.ErrNoEntries - } - return err - } - return nil -} - func (a *accountDB) GetLocalAccountByUsername(username string, account *gtsmodel.Account) db.DBError { if err := a.conn.Model(account).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select(); err != nil { if err == pg.ErrNoRows { diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index a851a4649..098513b96 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -31,6 +31,7 @@ import ( "github.com/go-pg/pg/extra/pgdebug" "github.com/go-pg/pg/v10" + "github.com/go-pg/pg/v10/orm" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -38,6 +39,11 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/id" ) +var registerTables []interface{} = []interface{}{ + >smodel.StatusToEmoji{}, + >smodel.StatusToTag{}, +} + // postgresService satisfies the DB interface type postgresService struct { db.Account @@ -58,6 +64,11 @@ type postgresService struct { // NewPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface. // Under the hood, it uses https://github.com/go-pg/pg to create and maintain a database connection. func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) { + for _, t := range registerTables { + // https://pg.uptrace.dev/orm/many-to-many-relation/ + orm.RegisterTable(t) + } + opts, err := derivePGOptions(c) if err != nil { return nil, fmt.Errorf("could not create postgres service: %s", err) diff --git a/internal/db/pg/status.go b/internal/db/pg/status.go index 5e1b55538..147f86320 100644 --- a/internal/db/pg/status.go +++ b/internal/db/pg/status.go @@ -22,6 +22,7 @@ import ( "container/list" "context" "errors" + "time" "github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10/orm" @@ -38,7 +39,7 @@ type statusDB struct { cancel context.CancelFunc } -func (s *statusDB) newStatusQ(status *gtsmodel.Status) *orm.Query { +func (s *statusDB) newStatusQ(status interface{}) *orm.Query { return s.conn.Model(status). Relation("Attachments"). Relation("Tags"). @@ -52,15 +53,11 @@ func (s *statusDB) newStatusQ(status *gtsmodel.Status) *orm.Query { Relation("CreatedWithApplication") } -func (s *statusDB) processStatusResponse(status *gtsmodel.Status, err error) (*gtsmodel.Status, db.DBError) { - switch err { - case pg.ErrNoRows: - return nil, db.ErrNoEntries - case nil: - return status, nil - default: - return nil, err - } +func (s *statusDB) newFaveQ(faves interface{}) *orm.Query { + return s.conn.Model(faves). + Relation("Account"). + Relation("TargetAccount"). + Relation("Status") } func (s *statusDB) GetStatusByID(id string) (*gtsmodel.Status, db.DBError) { @@ -69,7 +66,9 @@ func (s *statusDB) GetStatusByID(id string) (*gtsmodel.Status, db.DBError) { q := s.newStatusQ(status). Where("status.id = ?", id) - return s.processStatusResponse(status, q.Select()) + err := processErrorResponse(q.Select()) + + return status, err } func (s *statusDB) GetStatusByURI(uri string) (*gtsmodel.Status, db.DBError) { @@ -78,10 +77,52 @@ func (s *statusDB) GetStatusByURI(uri string) (*gtsmodel.Status, db.DBError) { q := s.newStatusQ(status). Where("LOWER(status.uri) = LOWER(?)", uri) - return s.processStatusResponse(status, q.Select()) + err := processErrorResponse(q.Select()) + + return status, err } -func (s *statusDB) StatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.DBError) { +func (s *statusDB) PutStatus(status *gtsmodel.Status) db.DBError { + transaction := func(tx *pg.Tx) error { + // create links between this status and any emojis it uses + for _, i := range status.EmojiIDs { + if _, err := tx.Model(>smodel.StatusToEmoji{ + StatusID: status.ID, + EmojiID: i, + }).Insert(); err != nil { + return err + } + } + + // create links between this status and any tags it uses + for _, i := range status.TagIDs { + if _, err := tx.Model(>smodel.StatusToTag{ + StatusID: status.ID, + TagID: i, + }).Insert(); err != nil { + return err + } + } + + // change the status ID of the media attachments to the new status + for _, a := range status.Attachments { + a.StatusID = status.ID + a.UpdatedAt = time.Now() + if _, err := s.conn.Model(a). + Where("id = ?", a.ID). + Update(); err != nil { + return err + } + } + + _, err := tx.Model(status).Insert() + return err + } + + return processErrorResponse(s.conn.RunInTransaction(context.Background(), transaction)) +} + +func (s *statusDB) GetStatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.DBError) { parents := []*gtsmodel.Status{} s.statusParent(status, &parents, onlyDirect) @@ -93,18 +134,19 @@ func (s *statusDB) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmo return } - parentStatus := >smodel.Status{} - if err := s.conn.Model(parentStatus).Where("id = ?", status.InReplyToID).Select(); err == nil { + parentStatus, err := s.GetStatusByID(status.InReplyToID) + if err == nil { *foundStatuses = append(*foundStatuses, parentStatus) } if onlyDirect { return } + s.statusParent(parentStatus, foundStatuses, false) } -func (s *statusDB) StatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, db.DBError) { +func (s *statusDB) GetStatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, db.DBError) { foundStatuses := &list.List{} foundStatuses.PushFront(status) s.statusChildren(status, foundStatuses, onlyDirect, minID) @@ -159,78 +201,52 @@ func (s *statusDB) statusChildren(status *gtsmodel.Status, foundStatuses *list.L } } -func (s *statusDB) GetReplyCountForStatus(status *gtsmodel.Status) (int, db.DBError) { +func (s *statusDB) CountStatusReplies(status *gtsmodel.Status) (int, db.DBError) { return s.conn.Model(>smodel.Status{}).Where("in_reply_to_id = ?", status.ID).Count() } -func (s *statusDB) GetReblogCountForStatus(status *gtsmodel.Status) (int, db.DBError) { +func (s *statusDB) CountStatusReblogs(status *gtsmodel.Status) (int, db.DBError) { return s.conn.Model(>smodel.Status{}).Where("boost_of_id = ?", status.ID).Count() } -func (s *statusDB) GetFaveCountForStatus(status *gtsmodel.Status) (int, db.DBError) { +func (s *statusDB) CountStatusFaves(status *gtsmodel.Status) (int, db.DBError) { return s.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Count() } -func (s *statusDB) StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { +func (s *statusDB) IsStatusFavedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { return s.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() } -func (s *statusDB) StatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { +func (s *statusDB) IsStatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { return s.conn.Model(>smodel.Status{}).Where("boost_of_id = ?", status.ID).Where("account_id = ?", accountID).Exists() } -func (s *statusDB) StatusMutedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { +func (s *statusDB) IsStatusMutedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { return s.conn.Model(>smodel.StatusMute{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() } -func (s *statusDB) StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { +func (s *statusDB) IsStatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, db.DBError) { return s.conn.Model(>smodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists() } -func (s *statusDB) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, db.DBError) { - accounts := []*gtsmodel.Account{} - +func (s *statusDB) GetStatusFaves(status *gtsmodel.Status) ([]*gtsmodel.StatusFave, db.DBError) { faves := []*gtsmodel.StatusFave{} - if err := s.conn.Model(&faves).Where("status_id = ?", status.ID).Select(); err != nil { - if err == pg.ErrNoRows { - return accounts, nil // no rows just means nobody has faved this status, so that's fine - } - return nil, err // an actual error has occurred - } - for _, f := range faves { - acc := >smodel.Account{} - if err := s.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil { - if err == pg.ErrNoRows { - continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it - } - return nil, err // an actual error has occurred - } - accounts = append(accounts, acc) - } - return accounts, nil + q := s.newFaveQ(&faves). + Where("status_id = ?", status.ID) + + err := processErrorResponse(q.Select()) + + return faves, err } -func (s *statusDB) WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, db.DBError) { - accounts := []*gtsmodel.Account{} +func (s *statusDB) GetStatusReblogs(status *gtsmodel.Status) ([]*gtsmodel.Status, db.DBError) { + reblogs := []*gtsmodel.Status{} - boosts := []*gtsmodel.Status{} - if err := s.conn.Model(&boosts).Where("boost_of_id = ?", status.ID).Select(); err != nil { - if err == pg.ErrNoRows { - return accounts, nil // no rows just means nobody has boosted this status, so that's fine - } - return nil, err // an actual error has occurred - } + q := s.newStatusQ(&reblogs). + Where("boost_of_id = ?", status.ID) - for _, f := range boosts { - acc := >smodel.Account{} - if err := s.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil { - if err == pg.ErrNoRows { - continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it - } - return nil, err // an actual error has occurred - } - accounts = append(accounts, acc) - } - return accounts, nil + err := processErrorResponse(q.Select()) + + return reblogs, err } diff --git a/internal/db/pg/status_test.go b/internal/db/pg/status_test.go index eefc0bb46..da7299dfa 100644 --- a/internal/db/pg/status_test.go +++ b/internal/db/pg/status_test.go @@ -80,6 +80,7 @@ func (suite *PGStandardTestSuite) TestGetStatusByURI() { suite.Nil(status.InReplyTo) suite.Nil(status.InReplyToAccount) } + func (suite *PGStandardTestSuite) TestGetStatusWithExtras() { status, err := suite.db.GetStatusByID(suite.testStatuses["admin_account_status_1"].ID) if err != nil { @@ -92,6 +93,7 @@ func (suite *PGStandardTestSuite) TestGetStatusWithExtras() { suite.NotEmpty(status.Attachments) suite.NotEmpty(status.Emojis) } + func TestStatusTestSuite(t *testing.T) { suite.Run(t, new(PGStandardTestSuite)) } diff --git a/internal/db/pg/util.go b/internal/db/pg/util.go new file mode 100644 index 000000000..e6d901961 --- /dev/null +++ b/internal/db/pg/util.go @@ -0,0 +1,25 @@ +package pg + +import ( + "strings" + + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/db" +) + +// processErrorResponse parses the given error and returns an appropriate DBError. +func processErrorResponse(err error) db.DBError { + switch err { + case nil: + return nil + case pg.ErrNoRows: + return db.ErrNoEntries + case pg.ErrMultiRows: + return db.ErrMultipleEntries + default: + if strings.Contains(err.Error(), "duplicate key value violates unique constraint") { + return db.ErrAlreadyExists + } + return err + } +} diff --git a/internal/db/status.go b/internal/db/status.go index df039e0d9..4f8e94dd8 100644 --- a/internal/db/status.go +++ b/internal/db/status.go @@ -27,42 +27,45 @@ type Status interface { // GetStatusByURI returns one status from the database, with all rel fields populated (if possible). GetStatusByURI(uri string) (*gtsmodel.Status, DBError) - // GetReplyCountForStatus returns the amount of replies recorded for a status, or an error if something goes wrong - GetReplyCountForStatus(status *gtsmodel.Status) (int, DBError) + // PutStatus stores one status in the database. + PutStatus(status *gtsmodel.Status) DBError - // GetReblogCountForStatus returns the amount of reblogs/boosts recorded for a status, or an error if something goes wrong - GetReblogCountForStatus(status *gtsmodel.Status) (int, DBError) + // CountStatusReplies returns the amount of replies recorded for a status, or an error if something goes wrong + CountStatusReplies(status *gtsmodel.Status) (int, DBError) - // GetFaveCountForStatus returns the amount of faves/likes recorded for a status, or an error if something goes wrong - GetFaveCountForStatus(status *gtsmodel.Status) (int, DBError) + // CountStatusReblogs returns the amount of reblogs/boosts recorded for a status, or an error if something goes wrong + CountStatusReblogs(status *gtsmodel.Status) (int, DBError) - // StatusParents get the parent statuses of a given status. + // CountStatusFaves returns the amount of faves/likes recorded for a status, or an error if something goes wrong + CountStatusFaves(status *gtsmodel.Status) (int, DBError) + + // GetStatusParents get the parent statuses of a given status. // // If onlyDirect is true, only the immediate parent will be returned. - StatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, DBError) + GetStatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, DBError) - // StatusChildren gets the child statuses of a given status. + // GetStatusChildren gets the child statuses of a given status. // // If onlyDirect is true, only the immediate children will be returned. - StatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, DBError) + GetStatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, DBError) - // StatusFavedBy checks if a given status has been faved by a given account ID - StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, DBError) + // IsStatusFavedBy checks if a given status has been faved by a given account ID + IsStatusFavedBy(status *gtsmodel.Status, accountID string) (bool, DBError) - // StatusRebloggedBy checks if a given status has been reblogged/boosted by a given account ID - StatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, DBError) + // IsStatusRebloggedBy checks if a given status has been reblogged/boosted by a given account ID + IsStatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, DBError) - // StatusMutedBy checks if a given status has been muted by a given account ID - StatusMutedBy(status *gtsmodel.Status, accountID string) (bool, DBError) + // IsStatusMutedBy checks if a given status has been muted by a given account ID + IsStatusMutedBy(status *gtsmodel.Status, accountID string) (bool, DBError) - // StatusBookmarkedBy checks if a given status has been bookmarked by a given account ID - StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, DBError) + // IsStatusBookmarkedBy checks if a given status has been bookmarked by a given account ID + IsStatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, DBError) - // WhoFavedStatus returns a slice of accounts who faved the given status. + // GetStatusFaves returns a slice of faves/likes of the given status. // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. - WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, DBError) + GetStatusFaves(status *gtsmodel.Status) ([]*gtsmodel.StatusFave, DBError) - // WhoBoostedStatus returns a slice of accounts who boosted the given status. + // GetStatusReblogs returns a slice of statuses that are a boost/reblog of the given status. // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user. - WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, DBError) + GetStatusReblogs(status *gtsmodel.Status) ([]*gtsmodel.Status, DBError) } diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go index 72d2e44d7..ba6766061 100644 --- a/internal/federation/dereferencing/account.go +++ b/internal/federation/dereferencing/account.go @@ -29,7 +29,6 @@ import ( "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" - "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/transport" @@ -65,8 +64,8 @@ func (d *deref) GetRemoteAccount(username string, remoteAccountID *url.URL, refr new := true // check if we already have the account in our db - maybeAccount := >smodel.Account{} - if err := d.db.GetWhere([]db.Where{{Key: "uri", Value: remoteAccountID.String()}}, maybeAccount); err == nil { + maybeAccount, err := d.db.GetAccountByURI(remoteAccountID.String()) + if err == nil { // we've seen this account before so it's not new new = false if !refresh { diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index 902e9daaa..3687d2983 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -109,7 +109,7 @@ func (d *deref) GetRemoteStatus(username string, remoteStatusID *url.URL, refres return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error populating status fields: %s", err) } - if err := d.db.Put(gtsStatus); err != nil { + if err := d.db.PutStatus(gtsStatus); err != nil { return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error putting new status: %s", err) } } else { @@ -338,7 +338,10 @@ func (d *deref) populateStatusFields(status *gtsmodel.Status, requestingUsername } m.StatusID = status.ID + m.Status = status + m.OriginAccountID = status.Account.ID + m.OriginAccount = status.Account m.OriginAccountURI = status.Account.URI targetAccount, _, err := d.GetRemoteAccount(requestingUsername, uri, false) @@ -348,6 +351,7 @@ func (d *deref) populateStatusFields(status *gtsmodel.Status, requestingUsername // by this point, we know the targetAccount exists in our database with an ID :) m.TargetAccountID = targetAccount.ID + m.TargetAccount = targetAccount if err := d.db.Put(m); err != nil { return fmt.Errorf("error creating mention: %s", err) } @@ -357,11 +361,12 @@ func (d *deref) populateStatusFields(status *gtsmodel.Status, requestingUsername // status has replyToURI but we don't have an ID yet for the status it replies to if status.InReplyToURI != "" && status.InReplyToID == "" { - replyToStatus := >smodel.Status{} - if err := d.db.GetWhere([]db.Where{{Key: "uri", Value: status.InReplyToURI}}, replyToStatus); err == nil { + if replyToStatus, err := d.db.GetStatusByURI(status.InReplyToURI); err == nil { // we have the status status.InReplyToID = replyToStatus.ID + status.InReplyTo = replyToStatus status.InReplyToAccountID = replyToStatus.AccountID + status.InReplyToAccount = replyToStatus.Account } } diff --git a/internal/federation/federatingdb/create.go b/internal/federation/federatingdb/create.go index d5287136a..fb4353cd4 100644 --- a/internal/federation/federatingdb/create.go +++ b/internal/federation/federatingdb/create.go @@ -112,7 +112,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { } status.ID = statusID - if err := f.db.Put(status); err != nil { + if err := f.db.PutStatus(status); err != nil { if err == db.ErrAlreadyExists { // the status already exists in the database, which means we've already handled everything else, // so we can just return nil here and be done with it. diff --git a/internal/federation/federatingdb/followers.go b/internal/federation/federatingdb/followers.go index 1ed74db8d..9034e90fd 100644 --- a/internal/federation/federatingdb/followers.go +++ b/internal/federation/federatingdb/followers.go @@ -31,7 +31,8 @@ func (f *federatingDB) Followers(c context.Context, actorIRI *url.URL) (follower acct := >smodel.Account{} if util.IsUserPath(actorIRI) { - if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: actorIRI.String()}}, acct); err != nil { + acct, err = f.db.GetAccountByURI(actorIRI.String()) + if err != nil { return nil, fmt.Errorf("FOLLOWERS: db error getting account with uri %s: %s", actorIRI.String(), err) } } else if util.IsFollowersPath(actorIRI) { diff --git a/internal/federation/federatingdb/get.go b/internal/federation/federatingdb/get.go index 77a24bf43..0265080f9 100644 --- a/internal/federation/federatingdb/get.go +++ b/internal/federation/federatingdb/get.go @@ -43,8 +43,8 @@ func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, er l.Debug("entering GET function") if util.IsUserPath(id) { - acct := >smodel.Account{} - if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: id.String()}}, acct); err != nil { + acct, err := f.db.GetAccountByURI(id.String()) + if err != nil { return nil, err } l.Debug("is user path! returning account") diff --git a/internal/federation/federatingdb/util.go b/internal/federation/federatingdb/util.go index 9a3e60cf1..eac70d85c 100644 --- a/internal/federation/federatingdb/util.go +++ b/internal/federation/federatingdb/util.go @@ -97,8 +97,8 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (idURL *url.URL, e for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() { // take the IRI of the first actor we can find (there should only be one) if iter.IsIRI() { - actorAccount := >smodel.Account{} - if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: iter.GetIRI().String()}}, actorAccount); err == nil { // if there's an error here, just use the fallback behavior -- we don't need to return an error here + // if there's an error here, just use the fallback behavior -- we don't need to return an error here + if actorAccount, err := f.db.GetAccountByURI(iter.GetIRI().String()); err == nil { newID, err := id.NewRandomULID() if err != nil { return nil, err diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index be988cda2..36179dc76 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -200,8 +200,8 @@ func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er return true, nil } - requestingAccount := >smodel.Account{} - if err := f.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, requestingAccount); err != nil { + requestingAccount, err := f.db.GetAccountByURI(uri.String()) + if err != nil { if err == db.ErrNoEntries { // we don't have an entry for this account so it's not blocked // TODO: allow a different default to be set for this behavior diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index c722d5f42..c7af2bb76 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -37,13 +37,13 @@ type Status struct { Attachments []*MediaAttachment `pg:"rel:has-many"` // Database IDs of any tags used in this status TagIDs []string `pg:"tags,array"` - Tags []*Tag `pg:"rel:has-many"` + Tags []*Tag `pg:"many2many:status_to_tags"` // https://pg.uptrace.dev/orm/many-to-many-relation/ // Database IDs of any mentions in this status MentionIDs []string `pg:"mentions,array"` Mentions []*Mention `pg:"rel:has-many"` // Database IDs of any emojis used in this status EmojiIDs []string `pg:"emojis,array"` - Emojis []*Emoji `pg:"rel:many2many"` + Emojis []*Emoji `pg:"many2many:status_to_emojis"` // https://pg.uptrace.dev/orm/many-to-many-relation/ // when was this status created? CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` // when was this status updated? @@ -91,6 +91,18 @@ type Status struct { Pinned bool } +// StatusToTag is an intermediate struct to facilitate the many2many relationship between a status and one or more tags. +type StatusToTag struct { + StatusID string `pg:"unique:statustag"` + TagID string `pg:"unique:statustag"` +} + +// StatusToEmoji is an intermediate struct to facilitate the many2many relationship between a status and one or more emojis. +type StatusToEmoji struct { + StatusID string `pg:"unique:statusemoji"` + EmojiID string `pg:"unique:statusemoji"` +} + // Visibility represents the visibility granularity of a status. type Visibility string diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index 6d3c5d4a9..012360bff 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -28,7 +28,7 @@ type StatusFave struct { CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` // id of the account that created ('did') the fave AccountID string `pg:"type:CHAR(26),notnull"` - Account *Account `pg:"rel:belongs-to"` + Account *Account `pg:"rel:has-one"` // id the account owning the faved status TargetAccountID string `pg:"type:CHAR(26),notnull"` TargetAccount *Account `pg:"rel:has-one"` diff --git a/internal/gtsmodel/tag.go b/internal/gtsmodel/tag.go index c151e348f..27cce1c8b 100644 --- a/internal/gtsmodel/tag.go +++ b/internal/gtsmodel/tag.go @@ -27,7 +27,7 @@ type Tag struct { // Href of this tag, eg https://example.org/tags/somehashtag URL string // name of this tag -- the tag without the hash part - Name string `pg:",unique,pk,notnull"` + Name string `pg:",unique,notnull"` // Which account ID is the first one we saw using this tag? FirstSeenFromAccountID string `pg:"type:CHAR(26)"` // when was this tag created diff --git a/internal/processing/federation.go b/internal/processing/federation.go index 544ab3bc5..8e65ede91 100644 --- a/internal/processing/federation.go +++ b/internal/processing/federation.go @@ -321,7 +321,7 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername } else { // scenario 3 // get immediate children - replies, err := p.db.StatusChildren(s, true, minID) + replies, err := p.db.GetStatusChildren(s, true, minID) if err != nil { return nil, gtserror.NewErrorInternalError(err) } diff --git a/internal/processing/fromfederator.go b/internal/processing/fromfederator.go index a294377ea..c95c27778 100644 --- a/internal/processing/fromfederator.go +++ b/internal/processing/fromfederator.go @@ -100,7 +100,7 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er } incomingAnnounce.ID = incomingAnnounceID - if err := p.db.Put(incomingAnnounce); err != nil { + if err := p.db.PutStatus(incomingAnnounce); err != nil { if err != db.ErrNoEntries { return fmt.Errorf("error adding dereferenced announce to the db: %s", err) } diff --git a/internal/processing/status/boost.go b/internal/processing/status/boost.go index 5f50517c9..d7a62beb1 100644 --- a/internal/processing/status/boost.go +++ b/internal/processing/status/boost.go @@ -41,7 +41,7 @@ func (p *processor) Boost(requestingAccount *gtsmodel.Account, application *gtsm boostWrapperStatus.BoostOfAccount = targetStatus.Account // put the boost in the database - if err := p.db.Put(boostWrapperStatus); err != nil { + if err := p.db.PutStatus(boostWrapperStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } diff --git a/internal/processing/status/boostedby.go b/internal/processing/status/boostedby.go index 150e5e2b5..e65665a32 100644 --- a/internal/processing/status/boostedby.go +++ b/internal/processing/status/boostedby.go @@ -26,21 +26,20 @@ func (p *processor) BoostedBy(requestingAccount *gtsmodel.Account, targetStatusI return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) } - // get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff - favingAccounts, err := p.db.WhoBoostedStatus(targetStatus) + statusReblogs, err := p.db.GetStatusReblogs(targetStatus) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing who boosted status: %s", err)) } // filter the list so the user doesn't see accounts they blocked or which blocked them filteredAccounts := []*gtsmodel.Account{} - for _, acc := range favingAccounts { - blocked, err := p.db.Blocked(requestingAccount.ID, acc.ID, true) + for _, s := range statusReblogs { + blocked, err := p.db.Blocked(requestingAccount.ID, s.AccountID, true) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error checking blocks: %s", err)) } if !blocked { - filteredAccounts = append(filteredAccounts, acc) + filteredAccounts = append(filteredAccounts, s.Account) } } diff --git a/internal/processing/status/context.go b/internal/processing/status/context.go index d4197f6ab..43002545e 100644 --- a/internal/processing/status/context.go +++ b/internal/processing/status/context.go @@ -32,7 +32,7 @@ func (p *processor) Context(requestingAccount *gtsmodel.Account, targetStatusID Descendants: []apimodel.Status{}, } - parents, err := p.db.StatusParents(targetStatus, false) + parents, err := p.db.GetStatusParents(targetStatus, false) if err != nil { return nil, gtserror.NewErrorInternalError(err) } @@ -50,7 +50,7 @@ func (p *processor) Context(requestingAccount *gtsmodel.Account, targetStatusID return context.Ancestors[i].ID < context.Ancestors[j].ID }) - children, err := p.db.StatusChildren(targetStatus, false, "") + children, err := p.db.GetStatusChildren(targetStatus, false, "") if err != nil { return nil, gtserror.NewErrorInternalError(err) } diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index 30d7dcc88..fc112ed8b 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -38,27 +38,22 @@ func (p *processor) Create(account *gtsmodel.Account, application *gtsmodel.Appl Text: form.Status, } - // check if replyToID is ok if err := p.ProcessReplyToID(form, account.ID, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - // check if mediaIDs are ok if err := p.ProcessMediaIDs(form, account.ID, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - // check if visibility settings are ok if err := p.ProcessVisibility(form, account.Privacy, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - // handle language settings if err := p.ProcessLanguage(form, account.Language, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - // handle mentions if err := p.ProcessMentions(form, account.ID, newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } @@ -75,20 +70,11 @@ func (p *processor) Create(account *gtsmodel.Account, application *gtsmodel.Appl return nil, gtserror.NewErrorInternalError(err) } - // put the new status in the database, generating an ID for it in the process - if err := p.db.Put(newStatus); err != nil { + // put the new status in the database + if err := p.db.PutStatus(newStatus); err != nil { return nil, gtserror.NewErrorInternalError(err) } - // change the status ID of the media attachments to the new status - for _, a := range newStatus.Attachments { - a.StatusID = newStatus.ID - a.UpdatedAt = time.Now() - if err := p.db.UpdateByID(a.ID, a); err != nil { - return nil, gtserror.NewErrorInternalError(err) - } - } - // send it back to the processor for async processing p.fromClientAPI <- gtsmodel.FromClientAPI{ APObjectType: gtsmodel.ActivityStreamsNote, diff --git a/internal/processing/status/favedby.go b/internal/processing/status/favedby.go index 5ce2590ce..92a725035 100644 --- a/internal/processing/status/favedby.go +++ b/internal/processing/status/favedby.go @@ -26,21 +26,20 @@ func (p *processor) FavedBy(requestingAccount *gtsmodel.Account, targetStatusID return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) } - // get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff - favingAccounts, err := p.db.WhoFavedStatus(targetStatus) + statusFaves, err := p.db.GetStatusFaves(targetStatus) if err != nil { return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing who faved status: %s", err)) } // filter the list so the user doesn't see accounts they blocked or which blocked them filteredAccounts := []*gtsmodel.Account{} - for _, acc := range favingAccounts { - blocked, err := p.db.Blocked(requestingAccount.ID, acc.ID, true) + for _, fave := range statusFaves { + blocked, err := p.db.Blocked(requestingAccount.ID, fave.AccountID, true) if err != nil { return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking blocks: %s", err)) } if !blocked { - filteredAccounts = append(filteredAccounts, acc) + filteredAccounts = append(filteredAccounts, fave.Account) } } diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 4055dee2e..a16318df8 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -37,9 +37,8 @@ func (c *converter) ASRepresentationToAccount(accountable ap.Accountable, update } uri := uriProp.GetIRI() - acct := >smodel.Account{} if !update { - err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri.String()}}, acct) + acct, err := c.db.GetAccountByURI(uri.String()) if err == nil { // we already know this account so we can skip generating it return acct, nil @@ -51,7 +50,7 @@ func (c *converter) ASRepresentationToAccount(accountable ap.Accountable, update } // we don't know the account, or we're being told to update it, so we need to generate it from the person -- at least we already have the URI! - acct = >smodel.Account{} + acct := >smodel.Account{} acct.URI = uri.String() // Username aka preferredUsername @@ -225,8 +224,8 @@ func (c *converter) ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status } status.AccountURI = attributedTo.String() - statusOwner := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: attributedTo.String(), CaseInsensitive: true}}, statusOwner); err != nil { + statusOwner, err := c.db.GetAccountByURI(attributedTo.String()) + if err != nil { return nil, fmt.Errorf("couldn't get status owner from db: %s", err) } status.AccountID = statusOwner.ID @@ -241,18 +240,16 @@ func (c *converter) ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status status.InReplyToURI = inReplyToURI.String() // now we can check if we have the replied-to status in our db already - inReplyToStatus := >smodel.Status{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: inReplyToURI.String()}}, inReplyToStatus); err == nil { + if inReplyToStatus, err := c.db.GetStatusByURI(inReplyToURI.String()); err == nil { // we have the status in our database already - // so we can set these fields here and then... + // so we can set these fields here and now... status.InReplyToID = inReplyToStatus.ID status.InReplyToAccountID = inReplyToStatus.AccountID status.InReplyTo = inReplyToStatus - - // ... check if we've seen the account already - inReplyToAccount := >smodel.Account{} - if err := c.db.GetByID(inReplyToStatus.AccountID, inReplyToAccount); err == nil { - status.InReplyToAccount = inReplyToAccount + if status.InReplyToAccount == nil { + if inReplyToAccount, err := c.db.GetAccountByID(inReplyToStatus.AccountID); err == nil { + status.InReplyToAccount = inReplyToAccount + } } } } @@ -328,8 +325,8 @@ func (c *converter) ASFollowToFollowRequest(followable ap.Followable) (*gtsmodel if err != nil { return nil, errors.New("error extracting actor property from follow") } - originAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: origin.String()}}, originAccount); err != nil { + originAccount, err := c.db.GetAccountByURI(origin.String()) + if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -337,8 +334,8 @@ func (c *converter) ASFollowToFollowRequest(followable ap.Followable) (*gtsmodel if err != nil { return nil, errors.New("error extracting object property from follow") } - targetAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetAccount); err != nil { + targetAccount, err := c.db.GetAccountByURI(target.String()) + if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -362,8 +359,8 @@ func (c *converter) ASFollowToFollow(followable ap.Followable) (*gtsmodel.Follow if err != nil { return nil, errors.New("error extracting actor property from follow") } - originAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: origin.String()}}, originAccount); err != nil { + originAccount, err := c.db.GetAccountByURI(origin.String()) + if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -371,8 +368,8 @@ func (c *converter) ASFollowToFollow(followable ap.Followable) (*gtsmodel.Follow if err != nil { return nil, errors.New("error extracting object property from follow") } - targetAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetAccount); err != nil { + targetAccount, err := c.db.GetAccountByURI(target.String()) + if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -396,8 +393,8 @@ func (c *converter) ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, er if err != nil { return nil, errors.New("error extracting actor property from like") } - originAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: origin.String()}}, originAccount); err != nil { + originAccount, err := c.db.GetAccountByURI(origin.String()) + if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -406,24 +403,30 @@ func (c *converter) ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, er return nil, errors.New("error extracting object property from like") } - targetStatus := >smodel.Status{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetStatus); err != nil { + targetStatus, err := c.db.GetStatusByURI(target.String()) + if err != nil { return nil, fmt.Errorf("error extracting status with uri %s from the database: %s", target.String(), err) } - targetAccount := >smodel.Account{} - if err := c.db.GetByID(targetStatus.AccountID, targetAccount); err != nil { - return nil, fmt.Errorf("error extracting account with id %s from the database: %s", targetStatus.AccountID, err) + var targetAccount *gtsmodel.Account + if targetStatus.Account != nil { + targetAccount = targetStatus.Account + } else { + a, err := c.db.GetAccountByID(targetStatus.AccountID) + if err != nil { + return nil, fmt.Errorf("error extracting account with id %s from the database: %s", targetStatus.AccountID, err) + } + targetAccount = a } return >smodel.StatusFave{ - TargetAccountID: targetAccount.ID, - StatusID: targetStatus.ID, - AccountID: originAccount.ID, - URI: uri, - Status: targetStatus, - TargetAccount: targetAccount, - Account: originAccount, + AccountID: originAccount.ID, + Account: originAccount, + TargetAccountID: targetAccount.ID, + TargetAccount: targetAccount, + StatusID: targetStatus.ID, + Status: targetStatus, + URI: uri, }, nil } @@ -438,9 +441,9 @@ func (c *converter) ASBlockToBlock(blockable ap.Blockable) (*gtsmodel.Block, err if err != nil { return nil, errors.New("ASBlockToBlock: error extracting actor property from block") } - originAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: origin.String()}}, originAccount); err != nil { - return nil, fmt.Errorf("ASBlockToBlock: error extracting account with uri %s from the database: %s", origin.String(), err) + originAccount, err := c.db.GetAccountByURI(origin.String()) + if err != nil { + return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } target, err := ap.ExtractObject(blockable) @@ -448,9 +451,9 @@ func (c *converter) ASBlockToBlock(blockable ap.Blockable) (*gtsmodel.Block, err return nil, errors.New("ASBlockToBlock: error extracting object property from block") } - targetAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String(), CaseInsensitive: true}}, targetAccount); err != nil { - return nil, fmt.Errorf("ASBlockToBlock: error extracting account with uri %s from the database: %s", target.String(), err) + targetAccount, err := c.db.GetAccountByURI(target.String()) + if err != nil { + return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } return >smodel.Block{ @@ -473,7 +476,7 @@ func (c *converter) ASAnnounceToStatus(announceable ap.Announceable) (*gtsmodel. } uri := idProp.GetIRI().String() - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: uri}}, status); err == nil { + if status, err := c.db.GetStatusByURI(uri); err == nil { // we already have it, great, just return it as-is :) isNew = false return status, isNew, nil @@ -507,12 +510,13 @@ func (c *converter) ASAnnounceToStatus(announceable ap.Announceable) (*gtsmodel. // get the boosting account based on the URI // this should have been dereferenced already before we hit this point so we can confidently error out if we don't have it - boostingAccount := >smodel.Account{} - if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: actor.String()}}, boostingAccount); err != nil { + boostingAccount, err := c.db.GetAccountByURI(actor.String()) + if err != nil { return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error in db fetching account with uri %s: %s", actor.String(), err) } status.AccountID = boostingAccount.ID status.AccountURI = boostingAccount.URI + status.Account = boostingAccount // these will all be wrapped in the boosted status so set them empty here status.AttachmentIDs = []string{} @@ -552,7 +556,6 @@ func (c *converter) ASAnnounceToStatus(announceable ap.Announceable) (*gtsmodel. status.Visibility = visibility // the rest of the fields will be taken from the target status, but it's not our job to do the dereferencing here - return status, isNew, nil } diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 95bc3d3dc..552157d0c 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -77,23 +77,17 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e // count statuses statusesCount, err := c.db.CountAccountStatuses(a.ID) if err != nil { - return nil, fmt.Errorf("error getting last statuses: %s", err) + return nil, fmt.Errorf("error counting statuses: %s", err) } // check when the last status was - lastStatus := >smodel.Status{} - if err := c.db.GetAccountLastStatus(a.ID, lastStatus); err != nil { - if err != db.ErrNoEntries { - return nil, fmt.Errorf("error getting last status: %s", err) - } - } var lastStatusAt string - if lastStatus != nil { - lastStatusAt = lastStatus.CreatedAt.Format(time.RFC3339) + lastPosted, err := c.db.GetAccountLastPosted(a.ID) + if err == nil && !lastPosted.IsZero() { + lastStatusAt = lastPosted.Format(time.RFC3339) } // build the avatar and header URLs - var aviURL string var aviURLStatic string if a.AvatarMediaAttachment != nil { @@ -285,17 +279,17 @@ func (c *converter) TagToMasto(t *gtsmodel.Tag) (model.Tag, error) { } func (c *converter) StatusToMasto(s *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*model.Status, error) { - repliesCount, err := c.db.GetReplyCountForStatus(s) + repliesCount, err := c.db.CountStatusReplies(s) if err != nil { return nil, fmt.Errorf("error counting replies: %s", err) } - reblogsCount, err := c.db.GetReblogCountForStatus(s) + reblogsCount, err := c.db.CountStatusReblogs(s) if err != nil { return nil, fmt.Errorf("error counting reblogs: %s", err) } - favesCount, err := c.db.GetFaveCountForStatus(s) + favesCount, err := c.db.CountStatusFaves(s) if err != nil { return nil, fmt.Errorf("error counting faves: %s", err) } diff --git a/internal/typeutils/util.go b/internal/typeutils/util.go index 1e13f0713..5751fbc84 100644 --- a/internal/typeutils/util.go +++ b/internal/typeutils/util.go @@ -10,25 +10,25 @@ func (c *converter) interactionsWithStatusForAccount(s *gtsmodel.Status, request si := &statusInteractions{} if requestingAccount != nil { - faved, err := c.db.StatusFavedBy(s, requestingAccount.ID) + faved, err := c.db.IsStatusFavedBy(s, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) } si.Faved = faved - reblogged, err := c.db.StatusRebloggedBy(s, requestingAccount.ID) + reblogged, err := c.db.IsStatusRebloggedBy(s, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) } si.Reblogged = reblogged - muted, err := c.db.StatusMutedBy(s, requestingAccount.ID) + muted, err := c.db.IsStatusMutedBy(s, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) } si.Muted = muted - bookmarked, err := c.db.StatusBookmarkedBy(s, requestingAccount.ID) + bookmarked, err := c.db.IsStatusBookmarkedBy(s, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) } diff --git a/testrig/db.go b/testrig/db.go index 659a74ca2..c670103f1 100644 --- a/testrig/db.go +++ b/testrig/db.go @@ -40,6 +40,8 @@ var testModels []interface{} = []interface{}{ >smodel.MediaAttachment{}, >smodel.Mention{}, >smodel.Status{}, + >smodel.StatusToEmoji{}, + >smodel.StatusToTag{}, >smodel.StatusFave{}, >smodel.StatusBookmark{}, >smodel.StatusMute{}, @@ -133,7 +135,7 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) { } for _, v := range NewTestStatuses() { - if err := db.Put(v); err != nil { + if err := db.PutStatus(v); err != nil { panic(err) } }