diff options
author | 2021-07-09 18:32:48 +0200 | |
---|---|---|
committer | 2021-07-09 18:32:48 +0200 | |
commit | c7da64922f8b41daaee1cb8fc2961f7fa1336737 (patch) | |
tree | b1f9c946bd223267f87f2a77a7455974d8d5e5e9 /internal/db | |
parent | Docs (#94) (diff) | |
download | gotosocial-c7da64922f8b41daaee1cb8fc2961f7fa1336737.tar.xz |
favourites GET implementation (#95)
Diffstat (limited to 'internal/db')
-rw-r--r-- | internal/db/db.go | 19 | ||||
-rw-r--r-- | internal/db/pg/pg.go | 86 | ||||
-rw-r--r-- | internal/db/pg/timeline.go | 185 |
3 files changed, 201 insertions, 89 deletions
diff --git a/internal/db/db.go b/internal/db/db.go index 1d7ed8b58..0a3979df6 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -241,13 +241,26 @@ type DB interface { // 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, error) - // GetStatusesWhereFollowing returns a slice of statuses from accounts that are followed by the given account id. - GetStatusesWhereFollowing(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) + // GetHomeTimelineForAccount returns a slice of statuses from accounts that are followed by the given account id. + // + // Statuses should be returned in descending order of when they were created (newest first). + GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) - // GetPublicTimelineForAccount fetches the account's PUBLIC timline -- ie., posts and replies that are public. + // GetPublicTimelineForAccount fetches the account's PUBLIC timeline -- ie., posts and replies that are public. // It will use the given filters and try to return as many statuses as possible up to the limit. + // + // Statuses should be returned in descending order of when they were created (newest first). GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) + // GetFavedTimelineForAccount fetches the account's FAVED timeline -- ie., posts and replies that the requesting account has faved. + // It will use the given filters and try to return as many statuses as possible up to the limit. + // + // Note that unlike the other GetTimeline functions, the returned statuses will be arranged by their FAVE id, not the STATUS id. + // In other words, they'll be returned in descending order of when they were faved by the requesting user, not when they were created. + // + // Also note the extra return values, which correspond to the nextMaxID and prevMinID for building Link headers. + GetFavedTimelineForAccount(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, error) + // GetNotificationsForAccount returns a list of notifications that pertain to the given accountID. GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index 614968e22..ad75cef15 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -814,92 +814,6 @@ func (ps *postgresService) WhoBoostedStatus(status *gtsmodel.Status) ([]*gtsmode return accounts, nil } -func (ps *postgresService) GetStatusesWhereFollowing(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) { - statuses := []*gtsmodel.Status{} - - q := ps.conn.Model(&statuses) - - q = q.ColumnExpr("status.*"). - Join("JOIN follows AS f ON f.target_account_id = status.account_id"). - Where("f.account_id = ?", accountID). - Order("status.id DESC") - - if maxID != "" { - q = q.Where("status.id < ?", maxID) - } - - if sinceID != "" { - q = q.Where("status.id > ?", sinceID) - } - - if minID != "" { - q = q.Where("status.id > ?", minID) - } - - if local { - q = q.Where("status.local = ?", local) - } - - if limit > 0 { - q = q.Limit(limit) - } - - err := q.Select() - if err != nil { - if err == pg.ErrNoRows { - return nil, db.ErrNoEntries{} - } - return nil, err - } - - if len(statuses) == 0 { - return nil, db.ErrNoEntries{} - } - - return statuses, nil -} - -func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) { - statuses := []*gtsmodel.Status{} - - q := ps.conn.Model(&statuses). - Where("visibility = ?", gtsmodel.VisibilityPublic). - Where("? IS NULL", pg.Ident("in_reply_to_id")). - Where("? IS NULL", pg.Ident("in_reply_to_uri")). - Where("? IS NULL", pg.Ident("boost_of_id")). - Order("status.id DESC") - - if maxID != "" { - q = q.Where("status.id < ?", maxID) - } - - if sinceID != "" { - q = q.Where("status.id > ?", sinceID) - } - - if minID != "" { - q = q.Where("status.id > ?", minID) - } - - if local { - q = q.Where("status.local = ?", local) - } - - if limit > 0 { - q = q.Limit(limit) - } - - err := q.Select() - if err != nil { - if err == pg.ErrNoRows { - return nil, db.ErrNoEntries{} - } - return nil, err - } - - return statuses, nil -} - func (ps *postgresService) GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) { notifications := []*gtsmodel.Notification{} diff --git a/internal/db/pg/timeline.go b/internal/db/pg/timeline.go new file mode 100644 index 000000000..95acd4f38 --- /dev/null +++ b/internal/db/pg/timeline.go @@ -0,0 +1,185 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package pg + +import ( + "sort" + + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (ps *postgresService) GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) { + statuses := []*gtsmodel.Status{} + + q := ps.conn.Model(&statuses) + + q = q.ColumnExpr("status.*"). + Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id"). + Where("f.account_id = ?", accountID). + Order("status.id DESC") + + if maxID != "" { + q = q.Where("status.id < ?", maxID) + } + + if sinceID != "" { + q = q.Where("status.id > ?", sinceID) + } + + if minID != "" { + q = q.Where("status.id > ?", minID) + } + + if local { + q = q.Where("status.local = ?", local) + } + + if limit > 0 { + q = q.Limit(limit) + } + + err := q.Select() + if err != nil { + if err == pg.ErrNoRows { + return nil, db.ErrNoEntries{} + } + return nil, err + } + + if len(statuses) == 0 { + return nil, db.ErrNoEntries{} + } + + return statuses, nil +} + +func (ps *postgresService) GetPublicTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error) { + statuses := []*gtsmodel.Status{} + + q := ps.conn.Model(&statuses). + Where("visibility = ?", gtsmodel.VisibilityPublic). + Where("? IS NULL", pg.Ident("in_reply_to_id")). + Where("? IS NULL", pg.Ident("in_reply_to_uri")). + Where("? IS NULL", pg.Ident("boost_of_id")). + Order("status.id DESC") + + if maxID != "" { + q = q.Where("status.id < ?", maxID) + } + + if sinceID != "" { + q = q.Where("status.id > ?", sinceID) + } + + if minID != "" { + q = q.Where("status.id > ?", minID) + } + + if local { + q = q.Where("status.local = ?", local) + } + + if limit > 0 { + q = q.Limit(limit) + } + + err := q.Select() + if err != nil { + if err == pg.ErrNoRows { + return nil, db.ErrNoEntries{} + } + return nil, err + } + + if len(statuses) == 0 { + return nil, db.ErrNoEntries{} + } + + return statuses, nil +} + +// TODO optimize this query and the logic here, because it's slow as balls -- it takes like a literal second to return with a limit of 20! +// It might be worth serving it through a timeline instead of raw DB queries, like we do for Home feeds. +func (ps *postgresService) GetFavedTimelineForAccount(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, error) { + + faves := []*gtsmodel.StatusFave{} + + fq := ps.conn.Model(&faves). + Where("account_id = ?", accountID). + Order("id DESC") + + if maxID != "" { + fq = fq.Where("id < ?", maxID) + } + + if minID != "" { + fq = fq.Where("id > ?", minID) + } + + if limit > 0 { + fq = fq.Limit(limit) + } + + err := fq.Select() + if err != nil { + if err == pg.ErrNoRows { + return nil, "", "", db.ErrNoEntries{} + } + return nil, "", "", err + } + + if len(faves) == 0 { + return nil, "", "", db.ErrNoEntries{} + } + + // map[statusID]faveID -- we need this to sort statuses by fave ID rather than their own ID + statusesFavesMap := map[string]string{} + + in := []string{} + for _, f := range faves { + statusesFavesMap[f.StatusID] = f.ID + in = append(in, f.StatusID) + } + + statuses := []*gtsmodel.Status{} + err = ps.conn.Model(&statuses).Where("id IN (?)", pg.In(in)).Select() + if err != nil { + if err == pg.ErrNoRows { + return nil, "", "", db.ErrNoEntries{} + } + return nil, "", "", err + } + + if len(statuses) == 0 { + return nil, "", "", db.ErrNoEntries{} + } + + // arrange statuses by fave ID + sort.Slice(statuses, func(i int, j int) bool { + statusI := statuses[i] + statusJ := statuses[j] + return statusesFavesMap[statusI.ID] < statusesFavesMap[statusJ.ID] + }) + + nextMaxID := faves[len(faves)-1].ID + prevMinID := faves[0].ID + return statuses, nextMaxID, prevMinID, nil +} |