summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/db/bundb/timeline.go85
-rw-r--r--internal/db/bundb/timeline_test.go40
2 files changed, 98 insertions, 27 deletions
diff --git a/internal/db/bundb/timeline.go b/internal/db/bundb/timeline.go
index 1dc9dcf1b..b2af5583f 100644
--- a/internal/db/bundb/timeline.go
+++ b/internal/db/bundb/timeline.go
@@ -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(
diff --git a/internal/db/bundb/timeline_test.go b/internal/db/bundb/timeline_test.go
index b944bd9b4..4874c2b35 100644
--- a/internal/db/bundb/timeline_test.go
+++ b/internal/db/bundb/timeline_test.go
@@ -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()