mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 15:22:26 -05:00 
			
		
		
		
	[bugfix] more robust list timeline invalidation (#1995)
This commit is contained in:
		
					parent
					
						
							
								346ecabd07
							
						
					
				
			
			
				commit
				
					
						f4319740ab
					
				
			
		
					 15 changed files with 254 additions and 226 deletions
				
			
		|  | @ -73,7 +73,14 @@ func requiredError(key string) gtserror.WithCode { | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| func ParseLimit(value string, defaultValue int, max, min int) (int, gtserror.WithCode) { | func ParseLimit(value string, defaultValue int, max, min int) (int, gtserror.WithCode) { | ||||||
| 	return parseInt(value, defaultValue, max, min, LimitKey) | 	i, err := parseInt(value, defaultValue, max, min, LimitKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} else if i == 0 { | ||||||
|  | 		// treat 0 as an empty query | ||||||
|  | 		return defaultValue, nil | ||||||
|  | 	} | ||||||
|  | 	return i, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func ParseLocal(value string, defaultValue bool) (bool, gtserror.WithCode) { | func ParseLocal(value string, defaultValue bool) (bool, gtserror.WithCode) { | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								internal/cache/cache.go
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								internal/cache/cache.go
									
										
									
									
										vendored
									
									
								
							|  | @ -93,13 +93,14 @@ func (c *Caches) setuphooks() { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	c.GTS.EmojiCategory().SetInvalidateCallback(func(category *gtsmodel.EmojiCategory) { | 	c.GTS.EmojiCategory().SetInvalidateCallback(func(category *gtsmodel.EmojiCategory) { | ||||||
| 		// Invalidate entire emoji cache, | 		// Invalidate any emoji in this category. | ||||||
| 		// as we can't know which emojis | 		c.GTS.Emoji().Invalidate("CategoryID", category.ID) | ||||||
| 		// specifically this will effect. |  | ||||||
| 		c.GTS.Emoji().Clear() |  | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	c.GTS.Follow().SetInvalidateCallback(func(follow *gtsmodel.Follow) { | 	c.GTS.Follow().SetInvalidateCallback(func(follow *gtsmodel.Follow) { | ||||||
|  | 		// Invalidate any related list entries. | ||||||
|  | 		c.GTS.ListEntry().Invalidate("FollowID", follow.ID) | ||||||
|  | 
 | ||||||
| 		// Invalidate follow origin account ID cached visibility. | 		// Invalidate follow origin account ID cached visibility. | ||||||
| 		c.Visibility.Invalidate("ItemID", follow.AccountID) | 		c.Visibility.Invalidate("ItemID", follow.AccountID) | ||||||
| 		c.Visibility.Invalidate("RequesterID", follow.AccountID) | 		c.Visibility.Invalidate("RequesterID", follow.AccountID) | ||||||
|  | @ -122,6 +123,11 @@ func (c *Caches) setuphooks() { | ||||||
| 		c.GTS.Follow().Invalidate("ID", followReq.ID) | 		c.GTS.Follow().Invalidate("ID", followReq.ID) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
|  | 	c.GTS.List().SetInvalidateCallback(func(list *gtsmodel.List) { | ||||||
|  | 		// Invalidate all cached entries of this list. | ||||||
|  | 		c.GTS.ListEntry().Invalidate("ListID", list.ID) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
| 	c.GTS.Status().SetInvalidateCallback(func(status *gtsmodel.Status) { | 	c.GTS.Status().SetInvalidateCallback(func(status *gtsmodel.Status) { | ||||||
| 		// Invalidate status ID cached visibility. | 		// Invalidate status ID cached visibility. | ||||||
| 		c.Visibility.Invalidate("ItemID", status.ID) | 		c.Visibility.Invalidate("ItemID", status.ID) | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								internal/cache/gts.go
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								internal/cache/gts.go
									
										
									
									
										vendored
									
									
								
							|  | @ -262,6 +262,7 @@ func (c *GTSCaches) initEmoji() { | ||||||
| 		{Name: "URI"}, | 		{Name: "URI"}, | ||||||
| 		{Name: "Shortcode.Domain"}, | 		{Name: "Shortcode.Domain"}, | ||||||
| 		{Name: "ImageStaticURL"}, | 		{Name: "ImageStaticURL"}, | ||||||
|  | 		{Name: "CategoryID", Multi: true}, | ||||||
| 	}, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji { | 	}, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji { | ||||||
| 		e2 := new(gtsmodel.Emoji) | 		e2 := new(gtsmodel.Emoji) | ||||||
| 		*e2 = *e1 | 		*e2 = *e1 | ||||||
|  | @ -338,6 +339,8 @@ func (c *GTSCaches) initList() { | ||||||
| func (c *GTSCaches) initListEntry() { | func (c *GTSCaches) initListEntry() { | ||||||
| 	c.listEntry = result.New([]result.Lookup{ | 	c.listEntry = result.New([]result.Lookup{ | ||||||
| 		{Name: "ID"}, | 		{Name: "ID"}, | ||||||
|  | 		{Name: "ListID", Multi: true}, | ||||||
|  | 		{Name: "FollowID", Multi: true}, | ||||||
| 	}, func(l1 *gtsmodel.ListEntry) *gtsmodel.ListEntry { | 	}, func(l1 *gtsmodel.ListEntry) *gtsmodel.ListEntry { | ||||||
| 		l2 := new(gtsmodel.ListEntry) | 		l2 := new(gtsmodel.ListEntry) | ||||||
| 		*l2 = *l1 | 		*l2 = *l1 | ||||||
|  |  | ||||||
|  | @ -41,33 +41,6 @@ type listDB struct { | ||||||
| 	LIST FUNCTIONS | 	LIST FUNCTIONS | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| func (l *listDB) getList(ctx context.Context, lookup string, dbQuery func(*gtsmodel.List) error, keyParts ...any) (*gtsmodel.List, error) { |  | ||||||
| 	list, err := l.state.Caches.GTS.List().Load(lookup, func() (*gtsmodel.List, error) { |  | ||||||
| 		var list gtsmodel.List |  | ||||||
| 
 |  | ||||||
| 		// Not cached! Perform database query. |  | ||||||
| 		if err := dbQuery(&list); err != nil { |  | ||||||
| 			return nil, l.conn.ProcessError(err) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return &list, nil |  | ||||||
| 	}, keyParts...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err // already processed |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if gtscontext.Barebones(ctx) { |  | ||||||
| 		// Only a barebones model was requested. |  | ||||||
| 		return list, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err := l.state.DB.PopulateList(ctx, list); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return list, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *listDB) GetListByID(ctx context.Context, id string) (*gtsmodel.List, error) { | func (l *listDB) GetListByID(ctx context.Context, id string) (*gtsmodel.List, error) { | ||||||
| 	return l.getList( | 	return l.getList( | ||||||
| 		ctx, | 		ctx, | ||||||
|  | @ -82,6 +55,34 @@ func (l *listDB) GetListByID(ctx context.Context, id string) (*gtsmodel.List, er | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (l *listDB) getList(ctx context.Context, lookup string, dbQuery func(*gtsmodel.List) error, keyParts ...any) (*gtsmodel.List, error) { | ||||||
|  | 	list, err := l.state.Caches.GTS.List().Load(lookup, func() (*gtsmodel.List, error) { | ||||||
|  | 		var list gtsmodel.List | ||||||
|  | 
 | ||||||
|  | 		// Not cached! Perform database query. | ||||||
|  | 		if err := dbQuery(&list); err != nil { | ||||||
|  | 			return nil, l.conn.ProcessError(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return &list, nil | ||||||
|  | 	}, keyParts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// already processed | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if gtscontext.Barebones(ctx) { | ||||||
|  | 		// Only a barebones model was requested. | ||||||
|  | 		return list, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := l.state.DB.PopulateList(ctx, list); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return list, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (l *listDB) GetListsForAccountID(ctx context.Context, accountID string) ([]*gtsmodel.List, error) { | func (l *listDB) GetListsForAccountID(ctx context.Context, accountID string) ([]*gtsmodel.List, error) { | ||||||
| 	// Fetch IDs of all lists owned by this account. | 	// Fetch IDs of all lists owned by this account. | ||||||
| 	var listIDs []string | 	var listIDs []string | ||||||
|  | @ -107,8 +108,6 @@ func (l *listDB) GetListsForAccountID(ctx context.Context, accountID string) ([] | ||||||
| 			log.Errorf(ctx, "error fetching list %q: %v", id, err) | 			log.Errorf(ctx, "error fetching list %q: %v", id, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		// Append list. |  | ||||||
| 		lists = append(lists, list) | 		lists = append(lists, list) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -161,49 +160,89 @@ func (l *listDB) UpdateList(ctx context.Context, list *gtsmodel.List, columns .. | ||||||
| 		columns = append(columns, "updated_at") | 		columns = append(columns, "updated_at") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	defer func() { | ||||||
|  | 		// Invalidate all entries for this list ID. | ||||||
|  | 		l.state.Caches.GTS.ListEntry().Invalidate("ListID", list.ID) | ||||||
|  | 
 | ||||||
|  | 		// Invalidate this entire list's timeline. | ||||||
|  | 		if err := l.state.Timelines.List.RemoveTimeline(ctx, list.ID); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error invalidating list timeline: %q", err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
| 	return l.state.Caches.GTS.List().Store(list, func() error { | 	return l.state.Caches.GTS.List().Store(list, func() error { | ||||||
| 		if _, err := l.conn.NewUpdate(). | 		_, err := l.conn.NewUpdate(). | ||||||
| 			Model(list). | 			Model(list). | ||||||
| 			Where("? = ?", bun.Ident("list.id"), list.ID). | 			Where("? = ?", bun.Ident("list.id"), list.ID). | ||||||
| 			Column(columns...). | 			Column(columns...). | ||||||
| 			Exec(ctx); err != nil { | 			Exec(ctx) | ||||||
| 		return l.conn.ProcessError(err) | 		return l.conn.ProcessError(err) | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nil |  | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *listDB) DeleteListByID(ctx context.Context, id string) error { | func (l *listDB) DeleteListByID(ctx context.Context, id string) error { | ||||||
| 	defer l.state.Caches.GTS.List().Invalidate("ID", id) | 	// Load list by ID into cache to ensure we can perform | ||||||
| 
 | 	// all necessary cache invalidation hooks on removal. | ||||||
| 	// Select all entries that belong to this list. | 	_, err := l.GetListByID( | ||||||
| 	listEntries, err := l.state.DB.GetListEntries(ctx, id, "", "", "", 0) | 		// Don't populate the entry; | ||||||
|  | 		// we only want the list ID. | ||||||
|  | 		gtscontext.SetBarebones(ctx), | ||||||
|  | 		id, | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("error selecting entries from list %q: %w", id, err) | 		if errors.Is(err, db.ErrNoEntries) { | ||||||
|  | 			// Already gone. | ||||||
|  | 			return nil | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 	// Delete each list entry. This will |  | ||||||
| 	// invalidate the list timeline too. |  | ||||||
| 	for _, listEntry := range listEntries { |  | ||||||
| 		err := l.state.DB.DeleteListEntry(ctx, listEntry.ID) |  | ||||||
| 		if err != nil && !errors.Is(err, db.ErrNoEntries) { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	defer func() { | ||||||
|  | 		// Invalidate this list from cache. | ||||||
|  | 		l.state.Caches.GTS.List().Invalidate("ID", id) | ||||||
|  | 
 | ||||||
|  | 		// Invalidate this entire list's timeline. | ||||||
|  | 		if err := l.state.Timelines.List.RemoveTimeline(ctx, id); err != nil { | ||||||
|  | 			log.Errorf(ctx, "error invalidating list timeline: %q", err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	return l.conn.RunInTx(ctx, func(tx bun.Tx) error { | ||||||
|  | 		// Delete all entries attached to list. | ||||||
|  | 		if _, err := tx.NewDelete(). | ||||||
|  | 			Table("list_entries"). | ||||||
|  | 			Where("? = ?", bun.Ident("list_id"), id). | ||||||
|  | 			Exec(ctx); err != nil { | ||||||
|  | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	// Finally delete list itself from DB. | 		// Delete the list itself. | ||||||
| 	_, err = l.conn.NewDelete(). | 		_, err := tx.NewDelete(). | ||||||
| 			Table("lists"). | 			Table("lists"). | ||||||
| 			Where("? = ?", bun.Ident("id"), id). | 			Where("? = ?", bun.Ident("id"), id). | ||||||
| 			Exec(ctx) | 			Exec(ctx) | ||||||
| 	return l.conn.ProcessError(err) | 		return err | ||||||
|  | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| 	LIST ENTRY functions | 	LIST ENTRY functions | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
|  | func (l *listDB) GetListEntryByID(ctx context.Context, id string) (*gtsmodel.ListEntry, error) { | ||||||
|  | 	return l.getListEntry( | ||||||
|  | 		ctx, | ||||||
|  | 		"ID", | ||||||
|  | 		func(listEntry *gtsmodel.ListEntry) error { | ||||||
|  | 			return l.conn.NewSelect(). | ||||||
|  | 				Model(listEntry). | ||||||
|  | 				Where("? = ?", bun.Ident("list_entry.id"), id). | ||||||
|  | 				Scan(ctx) | ||||||
|  | 		}, | ||||||
|  | 		id, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (l *listDB) getListEntry(ctx context.Context, lookup string, dbQuery func(*gtsmodel.ListEntry) error, keyParts ...any) (*gtsmodel.ListEntry, error) { | func (l *listDB) getListEntry(ctx context.Context, lookup string, dbQuery func(*gtsmodel.ListEntry) error, keyParts ...any) (*gtsmodel.ListEntry, error) { | ||||||
| 	listEntry, err := l.state.Caches.GTS.ListEntry().Load(lookup, func() (*gtsmodel.ListEntry, error) { | 	listEntry, err := l.state.Caches.GTS.ListEntry().Load(lookup, func() (*gtsmodel.ListEntry, error) { | ||||||
| 		var listEntry gtsmodel.ListEntry | 		var listEntry gtsmodel.ListEntry | ||||||
|  | @ -232,20 +271,6 @@ func (l *listDB) getListEntry(ctx context.Context, lookup string, dbQuery func(* | ||||||
| 	return listEntry, nil | 	return listEntry, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *listDB) GetListEntryByID(ctx context.Context, id string) (*gtsmodel.ListEntry, error) { |  | ||||||
| 	return l.getListEntry( |  | ||||||
| 		ctx, |  | ||||||
| 		"ID", |  | ||||||
| 		func(listEntry *gtsmodel.ListEntry) error { |  | ||||||
| 			return l.conn.NewSelect(). |  | ||||||
| 				Model(listEntry). |  | ||||||
| 				Where("? = ?", bun.Ident("list_entry.id"), id). |  | ||||||
| 				Scan(ctx) |  | ||||||
| 		}, |  | ||||||
| 		id, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (l *listDB) GetListEntries(ctx context.Context, | func (l *listDB) GetListEntries(ctx context.Context, | ||||||
| 	listID string, | 	listID string, | ||||||
| 	maxID string, | 	maxID string, | ||||||
|  | @ -328,8 +353,6 @@ func (l *listDB) GetListEntries(ctx context.Context, | ||||||
| 			log.Errorf(ctx, "error fetching list entry %q: %v", id, err) | 			log.Errorf(ctx, "error fetching list entry %q: %v", id, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		// Append list entries. |  | ||||||
| 		listEntries = append(listEntries, listEntry) | 		listEntries = append(listEntries, listEntry) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -337,7 +360,7 @@ func (l *listDB) GetListEntries(ctx context.Context, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *listDB) GetListEntriesForFollowID(ctx context.Context, followID string) ([]*gtsmodel.ListEntry, error) { | func (l *listDB) GetListEntriesForFollowID(ctx context.Context, followID string) ([]*gtsmodel.ListEntry, error) { | ||||||
| 	entryIDs := []string{} | 	var entryIDs []string | ||||||
| 
 | 
 | ||||||
| 	if err := l.conn. | 	if err := l.conn. | ||||||
| 		NewSelect(). | 		NewSelect(). | ||||||
|  | @ -362,8 +385,6 @@ func (l *listDB) GetListEntriesForFollowID(ctx context.Context, followID string) | ||||||
| 			log.Errorf(ctx, "error fetching list entry %q: %v", id, err) | 			log.Errorf(ctx, "error fetching list entry %q: %v", id, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		// Append list entries. |  | ||||||
| 		listEntries = append(listEntries, listEntry) | 		listEntries = append(listEntries, listEntry) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -387,33 +408,42 @@ func (l *listDB) PopulateListEntry(ctx context.Context, listEntry *gtsmodel.List | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *listDB) PutListEntries(ctx context.Context, listEntries []*gtsmodel.ListEntry) error { | func (l *listDB) PutListEntries(ctx context.Context, entries []*gtsmodel.ListEntry) error { | ||||||
|  | 	defer func() { | ||||||
|  | 		// Collect unique list IDs from the entries. | ||||||
|  | 		listIDs := collate(func(i int) string { | ||||||
|  | 			return entries[i].ListID | ||||||
|  | 		}, len(entries)) | ||||||
|  | 
 | ||||||
|  | 		for _, id := range listIDs { | ||||||
|  | 			// Invalidate the timeline for the list this entry belongs to. | ||||||
|  | 			if err := l.state.Timelines.List.RemoveTimeline(ctx, id); err != nil { | ||||||
|  | 				log.Errorf(ctx, "error invalidating list timeline: %q", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	// Finally, insert each list entry into the database. | ||||||
| 	return l.conn.RunInTx(ctx, func(tx bun.Tx) error { | 	return l.conn.RunInTx(ctx, func(tx bun.Tx) error { | ||||||
| 		for _, listEntry := range listEntries { | 		for _, entry := range entries { | ||||||
| 			if _, err := tx. | 			if err := l.state.Caches.GTS.ListEntry().Store(entry, func() error { | ||||||
|  | 				_, err := tx. | ||||||
| 					NewInsert(). | 					NewInsert(). | ||||||
| 				Model(listEntry). | 					Model(entry). | ||||||
| 				Exec(ctx); err != nil { | 					Exec(ctx) | ||||||
|  | 				return err | ||||||
|  | 			}); err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 
 |  | ||||||
| 			// Invalidate the timeline for the list this entry belongs to. |  | ||||||
| 			if err := l.state.Timelines.List.RemoveTimeline(ctx, listEntry.ListID); err != nil { |  | ||||||
| 				log.Errorf(ctx, "PutListEntries: error invalidating list timeline: %q", err) |  | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *listDB) DeleteListEntry(ctx context.Context, id string) error { | func (l *listDB) DeleteListEntry(ctx context.Context, id string) error { | ||||||
| 	defer l.state.Caches.GTS.ListEntry().Invalidate("ID", id) | 	// Load list entry into cache to ensure we can perform | ||||||
| 
 | 	// all necessary cache invalidation hooks on removal. | ||||||
| 	// Load list entry into cache before attempting a delete, | 	entry, err := l.GetListEntryByID( | ||||||
| 	// as we need the followID from it in order to trigger |  | ||||||
| 	// timeline invalidation. |  | ||||||
| 	listEntry, err := l.GetListEntryByID( |  | ||||||
| 		// Don't populate the entry; | 		// Don't populate the entry; | ||||||
| 		// we only want the list ID. | 		// we only want the list ID. | ||||||
| 		gtscontext.SetBarebones(ctx), | 		gtscontext.SetBarebones(ctx), | ||||||
|  | @ -428,36 +458,39 @@ func (l *listDB) DeleteListEntry(ctx context.Context, id string) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	defer func() { | 	defer func() { | ||||||
|  | 		// Invalidate this list entry upon delete. | ||||||
|  | 		l.state.Caches.GTS.ListEntry().Invalidate("ID", id) | ||||||
|  | 
 | ||||||
| 		// Invalidate the timeline for the list this entry belongs to. | 		// Invalidate the timeline for the list this entry belongs to. | ||||||
| 		if err := l.state.Timelines.List.RemoveTimeline(ctx, listEntry.ListID); err != nil { | 		if err := l.state.Timelines.List.RemoveTimeline(ctx, entry.ListID); err != nil { | ||||||
| 			log.Errorf(ctx, "DeleteListEntry: error invalidating list timeline: %q", err) | 			log.Errorf(ctx, "error invalidating list timeline: %q", err) | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	if _, err := l.conn.NewDelete(). | 	// Finally delete the list entry. | ||||||
|  | 	_, err = l.conn.NewDelete(). | ||||||
| 		Table("list_entries"). | 		Table("list_entries"). | ||||||
| 		Where("? = ?", bun.Ident("id"), listEntry.ID). | 		Where("? = ?", bun.Ident("id"), id). | ||||||
| 		Exec(ctx); err != nil { | 		Exec(ctx) | ||||||
| 		return l.conn.ProcessError(err) | 	return err | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (l *listDB) DeleteListEntriesForFollowID(ctx context.Context, followID string) error { | func (l *listDB) DeleteListEntriesForFollowID(ctx context.Context, followID string) error { | ||||||
| 	// Fetch IDs of all entries that pertain to this follow. | 	var entryIDs []string | ||||||
| 	var listEntryIDs []string | 
 | ||||||
|  | 	// Fetch entry IDs for follow ID. | ||||||
| 	if err := l.conn. | 	if err := l.conn. | ||||||
| 		NewSelect(). | 		NewSelect(). | ||||||
| 		TableExpr("? AS ?", bun.Ident("list_entries"), bun.Ident("list_entry")). | 		Table("list_entries"). | ||||||
| 		Column("list_entry.id"). | 		Column("id"). | ||||||
| 		Where("? = ?", bun.Ident("list_entry.follow_id"), followID). | 		Where("? = ?", bun.Ident("follow_id"), followID). | ||||||
| 		Order("list_entry.id DESC"). | 		Order("id DESC"). | ||||||
| 		Scan(ctx, &listEntryIDs); err != nil { | 		Scan(ctx, &entryIDs); err != nil { | ||||||
| 		return l.conn.ProcessError(err) | 		return l.conn.ProcessError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, id := range listEntryIDs { | 	for _, id := range entryIDs { | ||||||
|  | 		// Delete each separately to trigger cache invalidations. | ||||||
| 		if err := l.DeleteListEntry(ctx, id); err != nil { | 		if err := l.DeleteListEntry(ctx, id); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | @ -465,3 +498,24 @@ func (l *listDB) DeleteListEntriesForFollowID(ctx context.Context, followID stri | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // collate will collect the values of type T from an expected slice of length 'len', | ||||||
|  | // passing the expected index to each call of 'get' and deduplicating the end result. | ||||||
|  | func collate[T comparable](get func(int) T, len int) []T { | ||||||
|  | 	ts := make([]T, 0, len) | ||||||
|  | 	tm := make(map[T]struct{}, len) | ||||||
|  | 
 | ||||||
|  | 	for i := 0; i < len; i++ { | ||||||
|  | 		// Get next. | ||||||
|  | 		t := get(i) | ||||||
|  | 
 | ||||||
|  | 		if _, ok := tm[t]; !ok { | ||||||
|  | 			// New value, add | ||||||
|  | 			// to map + slice. | ||||||
|  | 			ts = append(ts, t) | ||||||
|  | 			tm[t] = struct{}{} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -328,7 +328,8 @@ func (r *relationshipDB) DeleteAccountFollows(ctx context.Context, accountID str | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Delete each follow from DB. | 		// Delete each follow from DB. | ||||||
| 		if err := r.deleteFollow(ctx, follow.ID); err != nil && !errors.Is(err, db.ErrNoEntries) { | 		if err := r.deleteFollow(ctx, follow.ID); err != nil && | ||||||
|  | 			!errors.Is(err, db.ErrNoEntries) { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -82,6 +82,7 @@ func (p *Processor) BookmarksGet(ctx context.Context, requestingAccount *gtsmode | ||||||
| 		if bookmark.ID < nextMaxIDValue { | 		if bookmark.ID < nextMaxIDValue { | ||||||
| 			nextMaxIDValue = bookmark.ID // Lowest ID (for paging down). | 			nextMaxIDValue = bookmark.ID // Lowest ID (for paging down). | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		if bookmark.ID > prevMinIDValue { | 		if bookmark.ID > prevMinIDValue { | ||||||
| 			prevMinIDValue = bookmark.ID // Highest ID (for paging up). | 			prevMinIDValue = bookmark.ID // Highest ID (for paging up). | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -94,27 +94,20 @@ func (p *Processor) StatusesGet( | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		items = make([]interface{}, 0, count) | 		items = make([]interface{}, 0, count) | ||||||
| 		nextMaxIDValue string |  | ||||||
| 		prevMinIDValue string |  | ||||||
| 	) |  | ||||||
| 
 | 
 | ||||||
| 	for i, s := range filtered { |  | ||||||
| 		// Set next + prev values before filtering and API | 		// Set next + prev values before filtering and API | ||||||
| 		// converting, so caller can still page properly. | 		// converting, so caller can still page properly. | ||||||
| 		if i == count-1 { | 		nextMaxIDValue = filtered[count-1].ID | ||||||
| 			nextMaxIDValue = s.ID | 		prevMinIDValue = filtered[0].ID | ||||||
| 		} | 	) | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = s.ID |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
|  | 	for _, s := range filtered { | ||||||
|  | 		// Convert filtered statuses to API statuses. | ||||||
| 		item, err := p.tc.StatusToAPIStatus(ctx, s, requestingAccount) | 		item, err := p.tc.StatusToAPIStatus(ctx, s, requestingAccount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping status %s because it couldn't be converted to its api representation: %s", s.ID, err) | 			log.Errorf(ctx, "error convering to api status: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		items = append(items, item) | 		items = append(items, item) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -172,22 +165,19 @@ func (p *Processor) WebStatusesGet(ctx context.Context, targetAccountID string, | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		items = make([]interface{}, 0, count) | 		items = make([]interface{}, 0, count) | ||||||
| 		nextMaxIDValue string |  | ||||||
| 	) |  | ||||||
| 
 | 
 | ||||||
| 	for i, s := range statuses { |  | ||||||
| 		// Set next value before API converting, | 		// Set next value before API converting, | ||||||
| 		// so caller can still page properly. | 		// so caller can still page properly. | ||||||
| 		if i == count-1 { | 		nextMaxIDValue = statuses[count-1].ID | ||||||
| 			nextMaxIDValue = s.ID | 	) | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
|  | 	for _, s := range statuses { | ||||||
|  | 		// Convert fetched statuses to API statuses. | ||||||
| 		item, err := p.tc.StatusToAPIStatus(ctx, s, nil) | 		item, err := p.tc.StatusToAPIStatus(ctx, s, nil) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping status %s because it couldn't be converted to its api representation: %s", s.ID, err) | 			log.Errorf(ctx, "error convering to api status: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		items = append(items, item) | 		items = append(items, item) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -54,22 +54,14 @@ func (p *Processor) ReportsGet( | ||||||
| 
 | 
 | ||||||
| 	count := len(reports) | 	count := len(reports) | ||||||
| 	items := make([]interface{}, 0, count) | 	items := make([]interface{}, 0, count) | ||||||
| 	nextMaxIDValue := "" | 	nextMaxIDValue := reports[count-1].ID | ||||||
| 	prevMinIDValue := "" | 	prevMinIDValue := reports[0].ID | ||||||
| 	for i, r := range reports { | 
 | ||||||
|  | 	for _, r := range reports { | ||||||
| 		item, err := p.tc.ReportToAdminAPIReport(ctx, r, account) | 		item, err := p.tc.ReportToAdminAPIReport(ctx, r, account) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err)) | 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err)) | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		if i == count-1 { |  | ||||||
| 			nextMaxIDValue = item.ID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = item.ID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		items = append(items, item) | 		items = append(items, item) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,7 +27,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Delete deletes one list for the given account. | // Delete deletes one list for the given account. | ||||||
| func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, id string) gtserror.WithCode { | func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, id string) gtserror.WithCode { | ||||||
| 	list, errWithCode := p.getList( | 	// Ensure list exists + is owned by requesting account. | ||||||
|  | 	_, errWithCode := p.getList( | ||||||
| 		// Use barebones ctx; no embedded | 		// Use barebones ctx; no embedded | ||||||
| 		// structs necessary for this call. | 		// structs necessary for this call. | ||||||
| 		gtscontext.SetBarebones(ctx), | 		gtscontext.SetBarebones(ctx), | ||||||
|  | @ -38,7 +39,7 @@ func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, id st | ||||||
| 		return errWithCode | 		return errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := p.state.DB.DeleteListByID(ctx, list.ID); err != nil { | 	if err := p.state.DB.DeleteListByID(ctx, id); err != nil { | ||||||
| 		return gtserror.NewErrorInternalError(err) | 		return gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -87,7 +87,14 @@ func (p *Processor) GetListAccounts( | ||||||
| 	limit int, | 	limit int, | ||||||
| ) (*apimodel.PageableResponse, gtserror.WithCode) { | ) (*apimodel.PageableResponse, gtserror.WithCode) { | ||||||
| 	// Ensure list exists + is owned by requesting account. | 	// Ensure list exists + is owned by requesting account. | ||||||
| 	if _, errWithCode := p.getList(ctx, account.ID, listID); errWithCode != nil { | 	_, errWithCode := p.getList( | ||||||
|  | 		// Use barebones ctx; no embedded | ||||||
|  | 		// structs necessary for this call. | ||||||
|  | 		gtscontext.SetBarebones(ctx), | ||||||
|  | 		account.ID, | ||||||
|  | 		listID, | ||||||
|  | 	) | ||||||
|  | 	if errWithCode != nil { | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -106,9 +113,12 @@ func (p *Processor) GetListAccounts( | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		items          = make([]interface{}, count) | 		items = make([]interface{}, 0, count) | ||||||
| 		nextMaxIDValue string | 
 | ||||||
| 		prevMinIDValue string | 		// Set next + prev values before filtering and API | ||||||
|  | 		// converting, so caller can still page properly. | ||||||
|  | 		nextMaxIDValue = listEntries[count-1].ID | ||||||
|  | 		prevMinIDValue = listEntries[0].ID | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	// For each list entry, we want the account it points to. | 	// For each list entry, we want the account it points to. | ||||||
|  | @ -117,37 +127,29 @@ func (p *Processor) GetListAccounts( | ||||||
| 	// from that follow. | 	// from that follow. | ||||||
| 	// | 	// | ||||||
| 	// We do paging not by account ID, but by list entry ID. | 	// We do paging not by account ID, but by list entry ID. | ||||||
| 	for i, listEntry := range listEntries { | 	for _, listEntry := range listEntries { | ||||||
| 		if i == count-1 { |  | ||||||
| 			nextMaxIDValue = listEntry.ID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = listEntry.ID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if err := p.state.DB.PopulateListEntry(ctx, listEntry); err != nil { | 		if err := p.state.DB.PopulateListEntry(ctx, listEntry); err != nil { | ||||||
| 			log.Debugf(ctx, "skipping list entry because of error populating it: %q", err) | 			log.Errorf(ctx, "error populating list entry: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err := p.state.DB.PopulateFollow(ctx, listEntry.Follow); err != nil { | 		if err := p.state.DB.PopulateFollow(ctx, listEntry.Follow); err != nil { | ||||||
| 			log.Debugf(ctx, "skipping list entry because of error populating follow: %q", err) | 			log.Errorf(ctx, "error populating follow: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, listEntry.Follow.TargetAccount) | 		apiAccount, err := p.tc.AccountToAPIAccountPublic(ctx, listEntry.Follow.TargetAccount) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping list entry because of error converting follow target account: %q", err) | 			log.Errorf(ctx, "error converting to public api account: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		items[i] = apiAccount | 		items = append(items, apiAccount) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return util.PackagePageableResponse(util.PageableResponseParams{ | 	return util.PackagePageableResponse(util.PageableResponseParams{ | ||||||
| 		Items:          items, | 		Items:          items, | ||||||
| 		Path:           "api/v1/lists/" + listID + "/accounts", | 		Path:           "/api/v1/lists/" + listID + "/accounts", | ||||||
| 		NextMaxIDValue: nextMaxIDValue, | 		NextMaxIDValue: nextMaxIDValue, | ||||||
| 		PrevMinIDValue: prevMinIDValue, | 		PrevMinIDValue: prevMinIDValue, | ||||||
| 		Limit:          limit, | 		Limit:          limit, | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ package report | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 
 | 
 | ||||||
|  | @ -64,31 +65,24 @@ func (p *Processor) GetMultiple( | ||||||
| 	limit int, | 	limit int, | ||||||
| ) (*apimodel.PageableResponse, gtserror.WithCode) { | ) (*apimodel.PageableResponse, gtserror.WithCode) { | ||||||
| 	reports, err := p.state.DB.GetReports(ctx, resolved, account.ID, targetAccountID, maxID, sinceID, minID, limit) | 	reports, err := p.state.DB.GetReports(ctx, resolved, account.ID, targetAccountID, maxID, sinceID, minID, limit) | ||||||
| 	if err != nil { | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
| 		if err == db.ErrNoEntries { |  | ||||||
| 			return util.EmptyPageableResponse(), nil |  | ||||||
| 		} |  | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	count := len(reports) | 	count := len(reports) | ||||||
|  | 	if count == 0 { | ||||||
|  | 		return util.EmptyPageableResponse(), nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	items := make([]interface{}, 0, count) | 	items := make([]interface{}, 0, count) | ||||||
| 	nextMaxIDValue := "" | 	nextMaxIDValue := reports[count-1].ID | ||||||
| 	prevMinIDValue := "" | 	prevMinIDValue := reports[0].ID | ||||||
| 	for i, r := range reports { | 
 | ||||||
|  | 	for _, r := range reports { | ||||||
| 		item, err := p.tc.ReportToAPIReport(ctx, r) | 		item, err := p.tc.ReportToAPIReport(ctx, r) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err)) | 			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting report to api: %s", err)) | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		if i == count-1 { |  | ||||||
| 			nextMaxIDValue = item.ID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = item.ID |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		items = append(items, item) | 		items = append(items, item) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ func (p *Processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, ma | ||||||
| 	for _, s := range statuses { | 	for _, s := range statuses { | ||||||
| 		visible, err := p.filter.StatusVisible(ctx, authed.Account, s) | 		visible, err := p.filter.StatusVisible(ctx, authed.Account, s) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping status %s because of an error checking status visibility: %s", s.ID, err) | 			log.Errorf(ctx, "error checking status visibility: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -56,7 +56,7 @@ func (p *Processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, ma | ||||||
| 
 | 
 | ||||||
| 		apiStatus, err := p.tc.StatusToAPIStatus(ctx, s, authed.Account) | 		apiStatus, err := p.tc.StatusToAPIStatus(ctx, s, authed.Account) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping status %s because it couldn't be converted to its api representation: %s", s.ID, err) | 			log.Errorf(ctx, "error convering to api status: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -65,7 +65,7 @@ func (p *Processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, ma | ||||||
| 
 | 
 | ||||||
| 	return util.PackagePageableResponse(util.PageableResponseParams{ | 	return util.PackagePageableResponse(util.PageableResponseParams{ | ||||||
| 		Items:          items, | 		Items:          items, | ||||||
| 		Path:           "api/v1/favourites", | 		Path:           "/api/v1/favourites", | ||||||
| 		NextMaxIDValue: nextMaxID, | 		NextMaxIDValue: nextMaxID, | ||||||
| 		PrevMinIDValue: prevMinID, | 		PrevMinIDValue: prevMinID, | ||||||
| 		Limit:          limit, | 		Limit:          limit, | ||||||
|  |  | ||||||
|  | @ -116,25 +116,17 @@ func (p *Processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, max | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		items          = make([]interface{}, count) | 		items          = make([]interface{}, count) | ||||||
| 		nextMaxIDValue string | 		nextMaxIDValue = statuses[count-1].GetID() | ||||||
| 		prevMinIDValue string | 		prevMinIDValue = statuses[0].GetID() | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	for i, item := range statuses { | 	for i := range statuses { | ||||||
| 		if i == count-1 { | 		items[i] = statuses[i] | ||||||
| 			nextMaxIDValue = item.GetID() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = item.GetID() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		items[i] = item |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return util.PackagePageableResponse(util.PageableResponseParams{ | 	return util.PackagePageableResponse(util.PageableResponseParams{ | ||||||
| 		Items:          items, | 		Items:          items, | ||||||
| 		Path:           "api/v1/timelines/home", | 		Path:           "/api/v1/timelines/home", | ||||||
| 		NextMaxIDValue: nextMaxIDValue, | 		NextMaxIDValue: nextMaxIDValue, | ||||||
| 		PrevMinIDValue: prevMinIDValue, | 		PrevMinIDValue: prevMinIDValue, | ||||||
| 		Limit:          limit, | 		Limit:          limit, | ||||||
|  |  | ||||||
|  | @ -142,25 +142,17 @@ func (p *Processor) ListTimelineGet(ctx context.Context, authed *oauth.Auth, lis | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		items          = make([]interface{}, count) | 		items          = make([]interface{}, count) | ||||||
| 		nextMaxIDValue string | 		nextMaxIDValue = statuses[count-1].GetID() | ||||||
| 		prevMinIDValue string | 		prevMinIDValue = statuses[0].GetID() | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	for i, item := range statuses { | 	for i := range statuses { | ||||||
| 		if i == count-1 { | 		items[i] = statuses[i] | ||||||
| 			nextMaxIDValue = item.GetID() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = item.GetID() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		items[i] = item |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return util.PackagePageableResponse(util.PageableResponseParams{ | 	return util.PackagePageableResponse(util.PageableResponseParams{ | ||||||
| 		Items:          items, | 		Items:          items, | ||||||
| 		Path:           "api/v1/timelines/list/" + listID, | 		Path:           "/api/v1/timelines/list/" + listID, | ||||||
| 		NextMaxIDValue: nextMaxIDValue, | 		NextMaxIDValue: nextMaxIDValue, | ||||||
| 		PrevMinIDValue: prevMinIDValue, | 		PrevMinIDValue: prevMinIDValue, | ||||||
| 		Limit:          limit, | 		Limit:          limit, | ||||||
|  |  | ||||||
|  | @ -44,24 +44,17 @@ func (p *Processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, m | ||||||
| 
 | 
 | ||||||
| 	var ( | 	var ( | ||||||
| 		items = make([]interface{}, 0, count) | 		items = make([]interface{}, 0, count) | ||||||
| 		nextMaxIDValue string |  | ||||||
| 		prevMinIDValue string |  | ||||||
| 	) |  | ||||||
| 
 | 
 | ||||||
| 	for i, s := range statuses { |  | ||||||
| 		// Set next + prev values before filtering and API | 		// Set next + prev values before filtering and API | ||||||
| 		// converting, so caller can still page properly. | 		// converting, so caller can still page properly. | ||||||
| 		if i == count-1 { | 		nextMaxIDValue = statuses[count-1].ID | ||||||
| 			nextMaxIDValue = s.ID | 		prevMinIDValue = statuses[0].ID | ||||||
| 		} | 	) | ||||||
| 
 |  | ||||||
| 		if i == 0 { |  | ||||||
| 			prevMinIDValue = s.ID |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
|  | 	for _, s := range statuses { | ||||||
| 		timelineable, err := p.filter.StatusPublicTimelineable(ctx, authed.Account, s) | 		timelineable, err := p.filter.StatusPublicTimelineable(ctx, authed.Account, s) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping status %s because of an error checking StatusPublicTimelineable: %s", s.ID, err) | 			log.Errorf(ctx, "error checking status visibility: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -71,7 +64,7 @@ func (p *Processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, m | ||||||
| 
 | 
 | ||||||
| 		apiStatus, err := p.tc.StatusToAPIStatus(ctx, s, authed.Account) | 		apiStatus, err := p.tc.StatusToAPIStatus(ctx, s, authed.Account) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Debugf(ctx, "skipping status %s because it couldn't be converted to its api representation: %s", s.ID, err) | 			log.Errorf(ctx, "error convert to api status: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -80,7 +73,7 @@ func (p *Processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, m | ||||||
| 
 | 
 | ||||||
| 	return util.PackagePageableResponse(util.PageableResponseParams{ | 	return util.PackagePageableResponse(util.PageableResponseParams{ | ||||||
| 		Items:          items, | 		Items:          items, | ||||||
| 		Path:           "api/v1/timelines/public", | 		Path:           "/api/v1/timelines/public", | ||||||
| 		NextMaxIDValue: nextMaxIDValue, | 		NextMaxIDValue: nextMaxIDValue, | ||||||
| 		PrevMinIDValue: prevMinIDValue, | 		PrevMinIDValue: prevMinIDValue, | ||||||
| 		Limit:          limit, | 		Limit:          limit, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue