[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
|
@ -50,6 +50,64 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
|
||||||
frontToBack = true
|
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.
|
q := t.db.
|
||||||
NewSelect().
|
NewSelect().
|
||||||
TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")).
|
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)
|
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
|
// Select only statuses authored by
|
||||||
// accounts with IDs in the slice.
|
// accounts with IDs in the slice.
|
||||||
q = q.Where(
|
q = q.Where(
|
||||||
|
|
|
@ -158,6 +158,46 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() {
|
||||||
suite.checkStatuses(s, id.Highest, id.Lowest, 20)
|
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() {
|
func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
|
||||||
var (
|
var (
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
|
|
Loading…
Reference in New Issue