mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-11-10 06:54:16 +00:00
[bugfix] Update home timeline query to ignore exclusive list entries (#3289)
* [bugfix] Update home timeline query to ignore exclusive list entries * a
This commit is contained in:
parent
d842069985
commit
20fe430ef9
2 changed files with 98 additions and 27 deletions
|
@ -50,6 +50,64 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
|
|||
frontToBack = true
|
||||
)
|
||||
|
||||
// As this is the home timeline, it should be
|
||||
// populated by statuses from accounts followed
|
||||
// by accountID, and posts from accountID itself.
|
||||
//
|
||||
// So, begin by seeing who accountID follows.
|
||||
// It should be a little cheaper to do this in
|
||||
// a separate query like this, rather than using
|
||||
// a join, since followIDs are cached in memory.
|
||||
follows, err := t.state.DB.GetAccountFollows(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
accountID,
|
||||
nil, // select all
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, gtserror.Newf("db error getting follows for account %s: %w", accountID, err)
|
||||
}
|
||||
|
||||
// To take account of exclusive lists, get all of
|
||||
// this account's lists, so we can filter out follows
|
||||
// that are in contained in exclusive lists.
|
||||
lists, err := t.state.DB.GetListsForAccountID(ctx, accountID)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, gtserror.Newf("db error getting lists for account %s: %w", accountID, err)
|
||||
}
|
||||
|
||||
// Index all follow IDs that fall in exclusive lists.
|
||||
ignoreFollowIDs := make(map[string]struct{})
|
||||
for _, list := range lists {
|
||||
if !*list.Exclusive {
|
||||
// Not exclusive,
|
||||
// we don't care.
|
||||
continue
|
||||
}
|
||||
|
||||
// Exclusive list, index all its follow IDs.
|
||||
for _, listEntry := range list.ListEntries {
|
||||
ignoreFollowIDs[listEntry.FollowID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract just the accountID from each follow,
|
||||
// ignoring follows that are in exclusive lists.
|
||||
targetAccountIDs := make([]string, 0, len(follows)+1)
|
||||
for _, f := range follows {
|
||||
_, ignore := ignoreFollowIDs[f.ID]
|
||||
if !ignore {
|
||||
targetAccountIDs = append(
|
||||
targetAccountIDs,
|
||||
f.TargetAccountID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Add accountID itself as a pseudo follow so that
|
||||
// accountID can see its own posts in the timeline.
|
||||
targetAccountIDs = append(targetAccountIDs, accountID)
|
||||
|
||||
// Now start building the database query.
|
||||
q := t.db.
|
||||
NewSelect().
|
||||
TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")).
|
||||
|
@ -89,33 +147,6 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
|
|||
q = q.Where("? = ?", bun.Ident("status.local"), local)
|
||||
}
|
||||
|
||||
// As this is the home timeline, it should be
|
||||
// populated by statuses from accounts followed
|
||||
// by accountID, and posts from accountID itself.
|
||||
//
|
||||
// So, begin by seeing who accountID follows.
|
||||
// It should be a little cheaper to do this in
|
||||
// a separate query like this, rather than using
|
||||
// a join, since followIDs are cached in memory.
|
||||
follows, err := t.state.DB.GetAccountFollows(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
accountID,
|
||||
nil, // select all
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, gtserror.Newf("db error getting follows for account %s: %w", accountID, err)
|
||||
}
|
||||
|
||||
// Extract just the accountID from each follow.
|
||||
targetAccountIDs := make([]string, len(follows)+1)
|
||||
for i, f := range follows {
|
||||
targetAccountIDs[i] = f.TargetAccountID
|
||||
}
|
||||
|
||||
// Add accountID itself as a pseudo follow so that
|
||||
// accountID can see its own posts in the timeline.
|
||||
targetAccountIDs[len(targetAccountIDs)-1] = accountID
|
||||
|
||||
// Select only statuses authored by
|
||||
// accounts with IDs in the slice.
|
||||
q = q.Where(
|
||||
|
|
|
@ -158,6 +158,46 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() {
|
|||
suite.checkStatuses(s, id.Highest, id.Lowest, 20)
|
||||
}
|
||||
|
||||
func (suite *TimelineTestSuite) TestGetHomeTimelineIgnoreExclusive() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
viewingAccount = suite.testAccounts["local_account_1"]
|
||||
)
|
||||
|
||||
// local_account_1_list_1 contains both admin_account
|
||||
// and local_account_2. If we mark this list as exclusive,
|
||||
// and remove the list entry for admin account, we should
|
||||
// only get statuses from zork and turtle in the timeline.
|
||||
list := new(gtsmodel.List)
|
||||
*list = *suite.testLists["local_account_1_list_1"]
|
||||
list.Exclusive = util.Ptr(true)
|
||||
if err := suite.db.UpdateList(ctx, list, "exclusive"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// First try with list just set to exclusive.
|
||||
// We should only get zork's own statuses.
|
||||
s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", "", 20, false)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.checkStatuses(s, id.Highest, id.Lowest, 8)
|
||||
|
||||
// Remove admin account from the exclusive list.
|
||||
listEntryID := suite.testListEntries["local_account_1_list_1_entry_2"].ID
|
||||
if err := suite.db.DeleteListEntry(ctx, listEntryID); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Zork should only see their own
|
||||
// statuses and admin's statuses now.
|
||||
s, err = suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", "", 20, false)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.checkStatuses(s, id.Highest, id.Lowest, 12)
|
||||
}
|
||||
|
||||
func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
|
|
Loading…
Reference in a new issue