mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 05:52:25 -05:00 
			
		
		
		
	[chore] local instance count query caching, improved status context endpoint logging, don't log ErrHideStatus when timelining (#3330)
* ensure that errors checking status visibility / converting aren't dropped * add some more context to error messages * include calling function name in log entries * don't error on timelining hidden status * further code to ignore statusfilter.ErrHideStatus type errors * remove unused error type * add local instance status / domain / user counts * add checks for localhost * rename from InstanceCounts to LocalInstance * improved code comment
This commit is contained in:
		
					parent
					
						
							
								964262b169
							
						
					
				
			
			
				commit
				
					
						4592e29087
					
				
			
		
					 9 changed files with 214 additions and 91 deletions
				
			
		
							
								
								
									
										11
									
								
								internal/cache/db.go
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								internal/cache/db.go
									
										
									
									
										vendored
									
									
								
							|  | @ -18,6 +18,8 @@ | ||||||
| package cache | package cache | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"sync/atomic" | ||||||
|  | 
 | ||||||
| 	"codeberg.org/gruf/go-structr" | 	"codeberg.org/gruf/go-structr" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/cache/domain" | 	"github.com/superseriousbusiness/gotosocial/internal/cache/domain" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
|  | @ -136,6 +138,14 @@ type DBCaches struct { | ||||||
| 	// Instance provides access to the gtsmodel Instance database cache. | 	// Instance provides access to the gtsmodel Instance database cache. | ||||||
| 	Instance StructCache[*gtsmodel.Instance] | 	Instance StructCache[*gtsmodel.Instance] | ||||||
| 
 | 
 | ||||||
|  | 	// LocalInstance provides caching for | ||||||
|  | 	// simple + common local instance queries. | ||||||
|  | 	LocalInstance struct { | ||||||
|  | 		Domains  atomic.Pointer[int] | ||||||
|  | 		Statuses atomic.Pointer[int] | ||||||
|  | 		Users    atomic.Pointer[int] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// InteractionRequest provides access to the gtsmodel InteractionRequest database cache. | 	// InteractionRequest provides access to the gtsmodel InteractionRequest database cache. | ||||||
| 	InteractionRequest StructCache[*gtsmodel.InteractionRequest] | 	InteractionRequest StructCache[*gtsmodel.InteractionRequest] | ||||||
| 
 | 
 | ||||||
|  | @ -852,6 +862,7 @@ func (c *Caches) initInstance() { | ||||||
| 		MaxSize:    cap, | 		MaxSize:    cap, | ||||||
| 		IgnoreErr:  ignoreErrors, | 		IgnoreErr:  ignoreErrors, | ||||||
| 		Copy:       copyF, | 		Copy:       copyF, | ||||||
|  | 		Invalidate: c.OnInvalidateInstance, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								internal/cache/invalidate.go
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								internal/cache/invalidate.go
									
										
									
									
										vendored
									
									
								
							|  | @ -19,6 +19,7 @@ package cache | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Below are cache invalidation hooks between other caches, | // Below are cache invalidation hooks between other caches, | ||||||
|  | @ -178,6 +179,11 @@ func (c *Caches) OnInvalidateFollowRequest(followReq *gtsmodel.FollowRequest) { | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *Caches) OnInvalidateInstance(instance *gtsmodel.Instance) { | ||||||
|  | 	// Invalidate the local domains count. | ||||||
|  | 	c.DB.LocalInstance.Domains.Store(nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *Caches) OnInvalidateList(list *gtsmodel.List) { | func (c *Caches) OnInvalidateList(list *gtsmodel.List) { | ||||||
| 	// Invalidate list IDs cache. | 	// Invalidate list IDs cache. | ||||||
| 	c.DB.ListIDs.Invalidate( | 	c.DB.ListIDs.Invalidate( | ||||||
|  | @ -255,6 +261,11 @@ func (c *Caches) OnInvalidateStatus(status *gtsmodel.Status) { | ||||||
| 		// Invalidate cache of attached poll ID. | 		// Invalidate cache of attached poll ID. | ||||||
| 		c.DB.Poll.Invalidate("ID", status.PollID) | 		c.DB.Poll.Invalidate("ID", status.PollID) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if util.PtrOrZero(status.Local) { | ||||||
|  | 		// Invalidate the local statuses count. | ||||||
|  | 		c.DB.LocalInstance.Statuses.Store(nil) | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Caches) OnInvalidateStatusBookmark(bookmark *gtsmodel.StatusBookmark) { | func (c *Caches) OnInvalidateStatusBookmark(bookmark *gtsmodel.StatusBookmark) { | ||||||
|  | @ -271,6 +282,9 @@ func (c *Caches) OnInvalidateUser(user *gtsmodel.User) { | ||||||
| 	// Invalidate local account ID cached visibility. | 	// Invalidate local account ID cached visibility. | ||||||
| 	c.Visibility.Invalidate("ItemID", user.AccountID) | 	c.Visibility.Invalidate("ItemID", user.AccountID) | ||||||
| 	c.Visibility.Invalidate("RequesterID", user.AccountID) | 	c.Visibility.Invalidate("RequesterID", user.AccountID) | ||||||
|  | 
 | ||||||
|  | 	// Invalidate the local users count. | ||||||
|  | 	c.DB.LocalInstance.Users.Store(nil) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Caches) OnInvalidateUserMute(mute *gtsmodel.UserMute) { | func (c *Caches) OnInvalidateUserMute(mute *gtsmodel.UserMute) { | ||||||
|  |  | ||||||
|  | @ -39,6 +39,15 @@ type instanceDB struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *instanceDB) CountInstanceUsers(ctx context.Context, domain string) (int, error) { | func (i *instanceDB) CountInstanceUsers(ctx context.Context, domain string) (int, error) { | ||||||
|  | 	localhost := (domain == config.GetHost() || domain == config.GetAccountDomain()) | ||||||
|  | 
 | ||||||
|  | 	if localhost { | ||||||
|  | 		// Check for a cached instance user count, if so return this. | ||||||
|  | 		if n := i.state.Caches.DB.LocalInstance.Users.Load(); n != nil { | ||||||
|  | 			return *n, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	q := i.db. | 	q := i.db. | ||||||
| 		NewSelect(). | 		NewSelect(). | ||||||
| 		TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")). | 		TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")). | ||||||
|  | @ -46,7 +55,7 @@ func (i *instanceDB) CountInstanceUsers(ctx context.Context, domain string) (int | ||||||
| 		Where("? != ?", bun.Ident("account.username"), domain). | 		Where("? != ?", bun.Ident("account.username"), domain). | ||||||
| 		Where("? IS NULL", bun.Ident("account.suspended_at")) | 		Where("? IS NULL", bun.Ident("account.suspended_at")) | ||||||
| 
 | 
 | ||||||
| 	if domain == config.GetHost() || domain == config.GetAccountDomain() { | 	if localhost { | ||||||
| 		// If the domain is *this* domain, just | 		// If the domain is *this* domain, just | ||||||
| 		// count where the domain field is null. | 		// count where the domain field is null. | ||||||
| 		q = q.Where("? IS NULL", bun.Ident("account.domain")) | 		q = q.Where("? IS NULL", bun.Ident("account.domain")) | ||||||
|  | @ -58,15 +67,30 @@ func (i *instanceDB) CountInstanceUsers(ctx context.Context, domain string) (int | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return 0, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if localhost { | ||||||
|  | 		// Update cached instance users account value. | ||||||
|  | 		i.state.Caches.DB.LocalInstance.Users.Store(&count) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return count, nil | 	return count, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) (int, error) { | func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) (int, error) { | ||||||
|  | 	localhost := (domain == config.GetHost() || domain == config.GetAccountDomain()) | ||||||
|  | 
 | ||||||
|  | 	if localhost { | ||||||
|  | 		// Check for a cached instance statuses count, if so return this. | ||||||
|  | 		if n := i.state.Caches.DB.LocalInstance.Statuses.Load(); n != nil { | ||||||
|  | 			return *n, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	q := i.db. | 	q := i.db. | ||||||
| 		NewSelect(). | 		NewSelect(). | ||||||
| 		TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")) | 		TableExpr("? AS ?", bun.Ident("statuses"), bun.Ident("status")) | ||||||
| 
 | 
 | ||||||
| 	if domain == config.GetHost() || domain == config.GetAccountDomain() { | 	if localhost { | ||||||
| 		// if the domain is *this* domain, just count where local is true | 		// if the domain is *this* domain, just count where local is true | ||||||
| 		q = q.Where("? = ?", bun.Ident("status.local"), true) | 		q = q.Where("? = ?", bun.Ident("status.local"), true) | ||||||
| 	} else { | 	} else { | ||||||
|  | @ -83,15 +107,30 @@ func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) ( | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return 0, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if localhost { | ||||||
|  | 		// Update cached instance statuses account value. | ||||||
|  | 		i.state.Caches.DB.LocalInstance.Statuses.Store(&count) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return count, nil | 	return count, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *instanceDB) CountInstanceDomains(ctx context.Context, domain string) (int, error) { | func (i *instanceDB) CountInstanceDomains(ctx context.Context, domain string) (int, error) { | ||||||
|  | 	localhost := (domain == config.GetHost() || domain == config.GetAccountDomain()) | ||||||
|  | 
 | ||||||
|  | 	if localhost { | ||||||
|  | 		// Check for a cached instance domains count, if so return this. | ||||||
|  | 		if n := i.state.Caches.DB.LocalInstance.Domains.Load(); n != nil { | ||||||
|  | 			return *n, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	q := i.db. | 	q := i.db. | ||||||
| 		NewSelect(). | 		NewSelect(). | ||||||
| 		TableExpr("? AS ?", bun.Ident("instances"), bun.Ident("instance")) | 		TableExpr("? AS ?", bun.Ident("instances"), bun.Ident("instance")) | ||||||
| 
 | 
 | ||||||
| 	if domain == config.GetHost() { | 	if localhost { | ||||||
| 		// if the domain is *this* domain, just count other instances it knows about | 		// if the domain is *this* domain, just count other instances it knows about | ||||||
| 		// exclude domains that are blocked | 		// exclude domains that are blocked | ||||||
| 		q = q. | 		q = q. | ||||||
|  | @ -106,6 +145,12 @@ func (i *instanceDB) CountInstanceDomains(ctx context.Context, domain string) (i | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, err | 		return 0, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if localhost { | ||||||
|  | 		// Update cached instance domains account value. | ||||||
|  | 		i.state.Caches.DB.LocalInstance.Domains.Store(&count) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return count, nil | 	return count, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -215,13 +260,15 @@ func (i *instanceDB) PopulateInstance(ctx context.Context, instance *gtsmodel.In | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *instanceDB) PutInstance(ctx context.Context, instance *gtsmodel.Instance) error { | func (i *instanceDB) PutInstance(ctx context.Context, instance *gtsmodel.Instance) error { | ||||||
| 	// Normalize the domain as punycode |  | ||||||
| 	var err error | 	var err error | ||||||
|  | 
 | ||||||
|  | 	// Normalize the domain as punycode | ||||||
| 	instance.Domain, err = util.Punify(instance.Domain) | 	instance.Domain, err = util.Punify(instance.Domain) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return gtserror.Newf("error punifying domain %s: %w", instance.Domain, err) | 		return gtserror.Newf("error punifying domain %s: %w", instance.Domain, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Store the new instance model in database, invalidating cache. | ||||||
| 	return i.state.Caches.DB.Instance.Store(instance, func() error { | 	return i.state.Caches.DB.Instance.Store(instance, func() error { | ||||||
| 		_, err := i.db.NewInsert().Model(instance).Exec(ctx) | 		_, err := i.db.NewInsert().Model(instance).Exec(ctx) | ||||||
| 		return err | 		return err | ||||||
|  |  | ||||||
|  | @ -104,18 +104,20 @@ func (f *Filter) isStatusVisible( | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if util.PtrOrValue(status.PendingApproval, false) { | 	if util.PtrOrZero(status.PendingApproval) { | ||||||
| 		// Use a different visibility heuristic | 		// Use a different visibility heuristic | ||||||
| 		// for pending approval statuses. | 		// for pending approval statuses. | ||||||
| 		return f.isPendingStatusVisible(ctx, | 		return isPendingStatusVisible( | ||||||
| 			requester, status, | 			requester, status, | ||||||
| 		) | 		), nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if requester == nil { | 	if requester == nil { | ||||||
| 		// Use a different visibility | 		// Use a different visibility | ||||||
| 		// heuristic for unauthed requests. | 		// heuristic for unauthed requests. | ||||||
| 		return f.isStatusVisibleUnauthed(ctx, status) | 		return f.isStatusVisibleUnauthed( | ||||||
|  | 			ctx, status, | ||||||
|  | 		) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
|  | @ -210,45 +212,42 @@ func (f *Filter) isStatusVisible( | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *Filter) isPendingStatusVisible( | // isPendingStatusVisible returns whether a status pending approval is visible to requester. | ||||||
| 	_ context.Context, | func isPendingStatusVisible(requester *gtsmodel.Account, status *gtsmodel.Status) bool { | ||||||
| 	requester *gtsmodel.Account, |  | ||||||
| 	status *gtsmodel.Status, |  | ||||||
| ) (bool, error) { |  | ||||||
| 	if requester == nil { | 	if requester == nil { | ||||||
| 		// Any old tom, dick, and harry can't | 		// Any old tom, dick, and harry can't | ||||||
| 		// see pending-approval statuses, | 		// see pending-approval statuses, | ||||||
| 		// no matter what their visibility. | 		// no matter what their visibility. | ||||||
| 		return false, nil | 		return false | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if status.AccountID == requester.ID { | 	if status.AccountID == requester.ID { | ||||||
| 		// This is requester's status, | 		// This is requester's status, | ||||||
| 		// so they can always see it. | 		// so they can always see it. | ||||||
| 		return true, nil | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if status.InReplyToAccountID == requester.ID { | 	if status.InReplyToAccountID == requester.ID { | ||||||
| 		// This status replies to requester, | 		// This status replies to requester, | ||||||
| 		// so they can always see it (else | 		// so they can always see it (else | ||||||
| 		// they can't approve it). | 		// they can't approve it). | ||||||
| 		return true, nil | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if status.BoostOfAccountID == requester.ID { | 	if status.BoostOfAccountID == requester.ID { | ||||||
| 		// This status boosts requester, | 		// This status boosts requester, | ||||||
| 		// so they can always see it. | 		// so they can always see it. | ||||||
| 		return true, nil | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Nobody else can see this. | 	// Nobody else | ||||||
| 	return false, nil | 	// can see this. | ||||||
|  | 	return false | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *Filter) isStatusVisibleUnauthed( | // isStatusVisibleUnauthed returns whether status is visible without any unauthenticated account. | ||||||
| 	ctx context.Context, | func (f *Filter) isStatusVisibleUnauthed(ctx context.Context, status *gtsmodel.Status) (bool, error) { | ||||||
| 	status *gtsmodel.Status, | 
 | ||||||
| ) (bool, error) { |  | ||||||
| 	// For remote accounts, only show | 	// For remote accounts, only show | ||||||
| 	// Public statuses via the web. | 	// Public statuses via the web. | ||||||
| 	if status.Account.IsRemote() { | 	if status.Account.IsRemote() { | ||||||
|  | @ -275,8 +274,7 @@ func (f *Filter) isStatusVisibleUnauthed( | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	webVisibility := status.Account.Settings.WebVisibility | 	switch webvis := status.Account.Settings.WebVisibility; webvis { | ||||||
| 	switch webVisibility { |  | ||||||
| 
 | 
 | ||||||
| 	// public_only: status must be Public. | 	// public_only: status must be Public. | ||||||
| 	case gtsmodel.VisibilityPublic: | 	case gtsmodel.VisibilityPublic: | ||||||
|  | @ -296,7 +294,7 @@ func (f *Filter) isStatusVisibleUnauthed( | ||||||
| 	default: | 	default: | ||||||
| 		return false, gtserror.Newf( | 		return false, gtserror.Newf( | ||||||
| 			"unrecognized web visibility for account %s: %s", | 			"unrecognized web visibility for account %s: %s", | ||||||
| 			status.Account.ID, webVisibility, | 			status.Account.ID, webvis, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -48,9 +48,6 @@ var ( | ||||||
| 
 | 
 | ||||||
| 	// ErrReservedAddr is returned if a dialed address resolves to an IP within a blocked or reserved net. | 	// ErrReservedAddr is returned if a dialed address resolves to an IP within a blocked or reserved net. | ||||||
| 	ErrReservedAddr = errors.New("dial within blocked / reserved IP range") | 	ErrReservedAddr = errors.New("dial within blocked / reserved IP range") | ||||||
| 
 |  | ||||||
| 	// ErrBodyTooLarge is returned when a received response body is above predefined limit (default 40MB). |  | ||||||
| 	ErrBodyTooLarge = errors.New("body size too large") |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Config provides configuration details for setting up a new | // Config provides configuration details for setting up a new | ||||||
|  | @ -302,7 +299,6 @@ func (c *Client) do(r *Request) (rsp *http.Response, retry bool, err error) { | ||||||
| 		if errorsv2.IsV2(err, | 		if errorsv2.IsV2(err, | ||||||
| 			context.DeadlineExceeded, | 			context.DeadlineExceeded, | ||||||
| 			context.Canceled, | 			context.Canceled, | ||||||
| 			ErrBodyTooLarge, |  | ||||||
| 			ErrReservedAddr, | 			ErrReservedAddr, | ||||||
| 		) { | 		) { | ||||||
| 			// Non-retryable errors. | 			// Non-retryable errors. | ||||||
|  |  | ||||||
|  | @ -42,6 +42,7 @@ func (p *Processor) GetTargetAccountBy( | ||||||
| 	// Fetch the target account from db. | 	// Fetch the target account from db. | ||||||
| 	target, err := getTargetFromDB() | 	target, err := getTargetFromDB() | ||||||
| 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
|  | 		err := gtserror.Newf("error getting from db: %w", err) | ||||||
| 		return nil, false, gtserror.NewErrorInternalError(err) | 		return nil, false, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -57,6 +58,7 @@ func (p *Processor) GetTargetAccountBy( | ||||||
| 	// Check whether target account is visible to requesting account. | 	// Check whether target account is visible to requesting account. | ||||||
| 	visible, err = p.visFilter.AccountVisible(ctx, requester, target) | 	visible, err = p.visFilter.AccountVisible(ctx, requester, target) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		err := gtserror.Newf("error checking visibility: %w", err) | ||||||
| 		return nil, false, gtserror.NewErrorInternalError(err) | 		return nil, false, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -128,7 +130,8 @@ func (p *Processor) GetVisibleTargetAccount( | ||||||
| 	return target, nil | 	return target, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetAPIAccount fetches the appropriate API account model depending on whether requester = target. | // GetAPIAccount fetches the appropriate API account | ||||||
|  | // model depending on whether requester = target. | ||||||
| func (p *Processor) GetAPIAccount( | func (p *Processor) GetAPIAccount( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	requester *gtsmodel.Account, | 	requester *gtsmodel.Account, | ||||||
|  | @ -148,14 +151,15 @@ func (p *Processor) GetAPIAccount( | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err := gtserror.Newf("error converting account: %w", err) | 		err := gtserror.Newf("error converting: %w", err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return apiAcc, nil | 	return apiAcc, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetAPIAccountBlocked fetches the limited "blocked" account model for given target. | // GetAPIAccountBlocked fetches the limited | ||||||
|  | // "blocked" account model for given target. | ||||||
| func (p *Processor) GetAPIAccountBlocked( | func (p *Processor) GetAPIAccountBlocked( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	targetAcc *gtsmodel.Account, | 	targetAcc *gtsmodel.Account, | ||||||
|  | @ -165,7 +169,7 @@ func (p *Processor) GetAPIAccountBlocked( | ||||||
| ) { | ) { | ||||||
| 	apiAccount, err := p.converter.AccountToAPIAccountBlocked(ctx, targetAcc) | 	apiAccount, err := p.converter.AccountToAPIAccountBlocked(ctx, targetAcc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = gtserror.Newf("error converting account: %w", err) | 		err := gtserror.Newf("error converting: %w", err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 	return apiAccount, nil | 	return apiAccount, nil | ||||||
|  | @ -182,7 +186,7 @@ func (p *Processor) GetAPIAccountSensitive( | ||||||
| ) { | ) { | ||||||
| 	apiAccount, err := p.converter.AccountToAPIAccountSensitive(ctx, targetAcc) | 	apiAccount, err := p.converter.AccountToAPIAccountSensitive(ctx, targetAcc) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = gtserror.Newf("error converting account: %w", err) | 		err := gtserror.Newf("error converting: %w", err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 	return apiAccount, nil | 	return apiAccount, nil | ||||||
|  | @ -226,8 +230,7 @@ func (p *Processor) getVisibleAPIAccounts( | ||||||
| ) []*apimodel.Account { | ) []*apimodel.Account { | ||||||
| 	// Start new log entry with | 	// Start new log entry with | ||||||
| 	// the above calling func's name. | 	// the above calling func's name. | ||||||
| 	l := log. | 	l := log.WithContext(ctx). | ||||||
| 		WithContext(ctx). |  | ||||||
| 		WithField("caller", log.Caller(calldepth+1)) | 		WithField("caller", log.Caller(calldepth+1)) | ||||||
| 
 | 
 | ||||||
| 	// Preallocate slice according to expected length. | 	// Preallocate slice according to expected length. | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" | 	"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" | ||||||
| 	statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" | 	statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/filter/usermute" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
|  | @ -50,6 +51,7 @@ func (p *Processor) GetTargetStatusBy( | ||||||
| 	// Fetch the target status from db. | 	// Fetch the target status from db. | ||||||
| 	target, err := getTargetFromDB() | 	target, err := getTargetFromDB() | ||||||
| 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
|  | 		err := gtserror.Newf("error getting from db: %w", err) | ||||||
| 		return nil, false, gtserror.NewErrorInternalError(err) | 		return nil, false, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -65,6 +67,7 @@ func (p *Processor) GetTargetStatusBy( | ||||||
| 	// Check whether target status is visible to requesting account. | 	// Check whether target status is visible to requesting account. | ||||||
| 	visible, err = p.visFilter.StatusVisible(ctx, requester, target) | 	visible, err = p.visFilter.StatusVisible(ctx, requester, target) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		err := gtserror.Newf("error checking visibility: %w", err) | ||||||
| 		return nil, false, gtserror.NewErrorInternalError(err) | 		return nil, false, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -174,14 +177,83 @@ func (p *Processor) GetAPIStatus( | ||||||
| 	apiStatus *apimodel.Status, | 	apiStatus *apimodel.Status, | ||||||
| 	errWithCode gtserror.WithCode, | 	errWithCode gtserror.WithCode, | ||||||
| ) { | ) { | ||||||
| 	apiStatus, err := p.converter.StatusToAPIStatus(ctx, target, requester, statusfilter.FilterContextNone, nil, nil) | 	apiStatus, err := p.converter.StatusToAPIStatus(ctx, | ||||||
|  | 		target, | ||||||
|  | 		requester, | ||||||
|  | 		statusfilter.FilterContextNone, | ||||||
|  | 		nil, | ||||||
|  | 		nil, | ||||||
|  | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		err = gtserror.Newf("error converting status: %w", err) | 		err := gtserror.Newf("error converting: %w", err) | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 	return apiStatus, nil | 	return apiStatus, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetVisibleAPIStatuses converts a slice of statuses to API | ||||||
|  | // model statuses, filtering according to visibility to requester | ||||||
|  | // along with given filter context, filters and user mutes. | ||||||
|  | // | ||||||
|  | // Please note that all errors will be logged at ERROR level, | ||||||
|  | // but will not be returned. Callers are likely to run into | ||||||
|  | // show-stopping errors in the lead-up to this function. | ||||||
|  | func (p *Processor) GetVisibleAPIStatuses( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	requester *gtsmodel.Account, | ||||||
|  | 	statuses []*gtsmodel.Status, | ||||||
|  | 	filterContext statusfilter.FilterContext, | ||||||
|  | 	filters []*gtsmodel.Filter, | ||||||
|  | 	userMutes []*gtsmodel.UserMute, | ||||||
|  | ) []apimodel.Status { | ||||||
|  | 
 | ||||||
|  | 	// Start new log entry with | ||||||
|  | 	// the calling function name | ||||||
|  | 	// as a field in each entry. | ||||||
|  | 	l := log.WithContext(ctx). | ||||||
|  | 		WithField("caller", log.Caller(3)) | ||||||
|  | 
 | ||||||
|  | 	// Compile mutes to useable user mutes for type converter. | ||||||
|  | 	compUserMutes := usermute.NewCompiledUserMuteList(userMutes) | ||||||
|  | 
 | ||||||
|  | 	// Iterate filtered statuses for conversion to API model. | ||||||
|  | 	apiStatuses := make([]apimodel.Status, 0, len(statuses)) | ||||||
|  | 	for _, status := range statuses { | ||||||
|  | 
 | ||||||
|  | 		// Check whether status is visible to requester. | ||||||
|  | 		visible, err := p.visFilter.StatusVisible(ctx, | ||||||
|  | 			requester, | ||||||
|  | 			status, | ||||||
|  | 		) | ||||||
|  | 		if err != nil { | ||||||
|  | 			l.Errorf("error checking visibility: %v", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if !visible { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Convert to API status, taking mute / filter into account. | ||||||
|  | 		apiStatus, err := p.converter.StatusToAPIStatus(ctx, | ||||||
|  | 			status, | ||||||
|  | 			requester, | ||||||
|  | 			filterContext, | ||||||
|  | 			filters, | ||||||
|  | 			compUserMutes, | ||||||
|  | 		) | ||||||
|  | 		if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) { | ||||||
|  | 			l.Errorf("error converting: %v", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Append converted status to return slice. | ||||||
|  | 		apiStatuses = append(apiStatuses, *apiStatus) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return apiStatuses | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // InvalidateTimelinedStatus is a shortcut function for invalidating the cached | // InvalidateTimelinedStatus is a shortcut function for invalidating the cached | ||||||
| // representation one status in the home timeline and all list timelines of the | // representation one status in the home timeline and all list timelines of the | ||||||
| // given accountID. It should only be called in cases where a status update | // given accountID. It should only be called in cases where a status update | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" | 	statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/usermute" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | @ -308,22 +307,7 @@ func (p *Processor) ContextGet( | ||||||
| 		return nil, gtserror.NewErrorInternalError(err) | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	convert := func( | 	// Retrieve the full thread context. | ||||||
| 		ctx context.Context, |  | ||||||
| 		status *gtsmodel.Status, |  | ||||||
| 		requestingAccount *gtsmodel.Account, |  | ||||||
| 	) (*apimodel.Status, error) { |  | ||||||
| 		return p.converter.StatusToAPIStatus( |  | ||||||
| 			ctx, |  | ||||||
| 			status, |  | ||||||
| 			requestingAccount, |  | ||||||
| 			statusfilter.FilterContextThread, |  | ||||||
| 			filters, |  | ||||||
| 			usermute.NewCompiledUserMuteList(mutes), |  | ||||||
| 		) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Retrieve the thread context. |  | ||||||
| 	threadContext, errWithCode := p.contextGet( | 	threadContext, errWithCode := p.contextGet( | ||||||
| 		ctx, | 		ctx, | ||||||
| 		requester, | 		requester, | ||||||
|  | @ -333,34 +317,27 @@ func (p *Processor) ContextGet( | ||||||
| 		return nil, errWithCode | 		return nil, errWithCode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	apiContext := &apimodel.ThreadContext{ | 	var apiContext apimodel.ThreadContext | ||||||
| 		Ancestors:   make([]apimodel.Status, 0, len(threadContext.ancestors)), |  | ||||||
| 		Descendants: make([]apimodel.Status, 0, len(threadContext.descendants)), |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Convert ancestors + filter | 	// Convert and filter the thread context ancestors. | ||||||
| 	// out ones that aren't visible. | 	apiContext.Ancestors = p.c.GetVisibleAPIStatuses(ctx, | ||||||
| 	for _, status := range threadContext.ancestors { | 		requester, | ||||||
| 		if v, err := p.visFilter.StatusVisible(ctx, requester, status); err == nil && v { | 		threadContext.ancestors, | ||||||
| 			status, err := convert(ctx, status, requester) | 		statusfilter.FilterContextThread, | ||||||
| 			if err == nil { | 		filters, | ||||||
| 				apiContext.Ancestors = append(apiContext.Ancestors, *status) | 		mutes, | ||||||
| 			} | 	) | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Convert descendants + filter | 	// Convert and filter the thread context descendants | ||||||
| 	// out ones that aren't visible. | 	apiContext.Descendants = p.c.GetVisibleAPIStatuses(ctx, | ||||||
| 	for _, status := range threadContext.descendants { | 		requester, | ||||||
| 		if v, err := p.visFilter.StatusVisible(ctx, requester, status); err == nil && v { | 		threadContext.descendants, | ||||||
| 			status, err := convert(ctx, status, requester) | 		statusfilter.FilterContextThread, | ||||||
| 			if err == nil { | 		filters, | ||||||
| 				apiContext.Descendants = append(apiContext.Descendants, *status) | 		mutes, | ||||||
| 			} | 	) | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return apiContext, nil | 	return &apiContext, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // WebContextGet is like ContextGet, but is explicitly | // WebContextGet is like ContextGet, but is explicitly | ||||||
|  |  | ||||||
|  | @ -384,8 +384,9 @@ func (s *Surface) timelineStatus( | ||||||
| ) (bool, error) { | ) (bool, error) { | ||||||
| 
 | 
 | ||||||
| 	// Ingest status into given timeline using provided function. | 	// Ingest status into given timeline using provided function. | ||||||
| 	if inserted, err := ingest(ctx, timelineID, status); err != nil { | 	if inserted, err := ingest(ctx, timelineID, status); err != nil && | ||||||
| 		err = gtserror.Newf("error ingesting status %s: %w", status.ID, err) | 		!errors.Is(err, statusfilter.ErrHideStatus) { | ||||||
|  | 		err := gtserror.Newf("error ingesting status %s: %w", status.ID, err) | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} else if !inserted { | 	} else if !inserted { | ||||||
| 		// Nothing more to do. | 		// Nothing more to do. | ||||||
|  | @ -400,15 +401,19 @@ func (s *Surface) timelineStatus( | ||||||
| 		filters, | 		filters, | ||||||
| 		mutes, | 		mutes, | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil && !errors.Is(err, statusfilter.ErrHideStatus) { | ||||||
| 		err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err) | 		err := gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err) | ||||||
| 		return true, err | 		return true, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if apiStatus != nil { | ||||||
| 		// The status was inserted so stream it to the user. | 		// The status was inserted so stream it to the user. | ||||||
| 		s.Stream.Update(ctx, account, apiStatus, streamType) | 		s.Stream.Update(ctx, account, apiStatus, streamType) | ||||||
| 
 |  | ||||||
| 		return true, nil | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Status was hidden. | ||||||
|  | 	return false, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // timelineAndNotifyStatusForTagFollowers inserts the status into the | // timelineAndNotifyStatusForTagFollowers inserts the status into the | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue