mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 04:32:25 -05: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…
	
	Add table
		Add a link
		
	
		Reference in a new issue