diff options
author | 2023-03-28 14:03:14 +0100 | |
---|---|---|
committer | 2023-03-28 14:03:14 +0100 | |
commit | de6e3e5f2a8ea639d76e310a11cb9bc093fef3a9 (patch) | |
tree | e2b7044e22c943425a4d351a02f862fbde783657 /internal/visibility/statusvisible.go | |
parent | [feature] Add list command to admin account (#1648) (diff) | |
download | gotosocial-de6e3e5f2a8ea639d76e310a11cb9bc093fef3a9.tar.xz |
[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
Diffstat (limited to 'internal/visibility/statusvisible.go')
-rw-r--r-- | internal/visibility/statusvisible.go | 252 |
1 files changed, 0 insertions, 252 deletions
diff --git a/internal/visibility/statusvisible.go b/internal/visibility/statusvisible.go deleted file mode 100644 index 91a1f6221..000000000 --- a/internal/visibility/statusvisible.go +++ /dev/null @@ -1,252 +0,0 @@ -// GoToSocial -// Copyright (C) GoToSocial Authors admin@gotosocial.org -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -package visibility - -import ( - "context" - "fmt" - - "codeberg.org/gruf/go-kv" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/log" -) - -func (f *filter) StatusVisible(ctx context.Context, targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account) (bool, error) { - l := log.WithContext(ctx). - WithFields(kv.Fields{{"statusID", targetStatus.ID}}...) - - // Fetch any relevant accounts for the target status - const getBoosted = true - relevantAccounts, err := f.relevantAccounts(ctx, targetStatus, getBoosted) - if err != nil { - l.Debugf("error pulling relevant accounts for status %s: %s", targetStatus.ID, err) - return false, fmt.Errorf("StatusVisible: error pulling relevant accounts for status %s: %s", targetStatus.ID, err) - } - - // Check we have determined a target account - targetAccount := relevantAccounts.Account - if targetAccount == nil { - l.Trace("target account is not set") - return false, nil - } - - // Check for domain blocks among relevant accounts - domainBlocked, err := f.domainBlockedRelevant(ctx, relevantAccounts) - if err != nil { - l.Debugf("error checking domain block: %s", err) - return false, fmt.Errorf("error checking domain block: %s", err) - } else if domainBlocked { - return false, nil - } - - // if target account is suspended then don't show the status - if !targetAccount.SuspendedAt.IsZero() { - l.Trace("target account suspended at is not zero") - return false, nil - } - - // if the target user doesn't exist (anymore) then the status also shouldn't be visible - // note: we only do this for local users - if targetAccount.Domain == "" { - targetUser, err := f.db.GetUserByAccountID(ctx, targetAccount.ID) - if err != nil { - l.Debug("target user could not be selected") - if err == db.ErrNoEntries { - return false, nil - } - return false, fmt.Errorf("StatusVisible: db error selecting user for local target account %s: %s", targetAccount.ID, err) - } - - // if target user is disabled, not yet approved, or not confirmed then don't show the status - // (although in the latter two cases it's unlikely they posted a status yet anyway, but you never know!) - if *targetUser.Disabled || !*targetUser.Approved || targetUser.ConfirmedAt.IsZero() { - l.Trace("target user is disabled, not approved, or not confirmed") - return false, nil - } - } - - // If requesting account is nil, that means whoever requested the status didn't auth, or their auth failed. - // In this case, we can still serve the status if it's public, otherwise we definitely shouldn't. - if requestingAccount == nil { - if targetStatus.Visibility == gtsmodel.VisibilityPublic { - return true, nil - } - l.Trace("requesting account is nil but the target status isn't public") - return false, nil - } - - // if the requesting user doesn't exist (anymore) then the status also shouldn't be visible - // note: we only do this for local users - if requestingAccount.Domain == "" { - requestingUser, err := f.db.GetUserByAccountID(ctx, requestingAccount.ID) - if err != nil { - // if the requesting account is local but doesn't have a corresponding user in the db this is a problem - l.Debug("requesting user could not be selected") - if err == db.ErrNoEntries { - return false, nil - } - return false, fmt.Errorf("StatusVisible: db error selecting user for local requesting account %s: %s", requestingAccount.ID, err) - } - // okay, user exists, so make sure it has full privileges/is confirmed/approved - if *requestingUser.Disabled || !*requestingUser.Approved || requestingUser.ConfirmedAt.IsZero() { - l.Trace("requesting account is local but corresponding user is either disabled, not approved, or not confirmed") - return false, nil - } - } - - // if requesting account is suspended then don't show the status -- although they probably shouldn't have gotten - // this far (ie., been authed) in the first place: this is just for safety. - if !requestingAccount.SuspendedAt.IsZero() { - l.Trace("requesting account is suspended") - return false, nil - } - - // if the target status belongs to the requesting account, they should always be able to view it at this point - if targetStatus.AccountID == requestingAccount.ID { - return true, nil - } - - // At this point we have a populated targetAccount, targetStatus, and requestingAccount, so we can check for blocks and whathaveyou - // First check if a block exists directly between the target account (which authored the status) and the requesting account. - if blocked, err := f.db.IsBlocked(ctx, targetAccount.ID, requestingAccount.ID, true); err != nil { - l.Debugf("something went wrong figuring out if the accounts have a block: %s", err) - return false, err - } else if blocked { - // don't allow the status to be viewed if a block exists in *either* direction between these two accounts, no creepy stalking please - l.Trace("a block exists between requesting account and target account") - return false, nil - } - - // If not in reply to the requesting account, check if inReplyToAccount is blocked - if relevantAccounts.InReplyToAccount != nil && relevantAccounts.InReplyToAccount.ID != requestingAccount.ID { - if blocked, err := f.db.IsBlocked(ctx, relevantAccounts.InReplyToAccount.ID, requestingAccount.ID, true); err != nil { - return false, err - } else if blocked { - l.Trace("a block exists between requesting account and reply to account") - return false, nil - } - } - - // status boosts accounts id - if relevantAccounts.BoostedAccount != nil { - if blocked, err := f.db.IsBlocked(ctx, relevantAccounts.BoostedAccount.ID, requestingAccount.ID, true); err != nil { - return false, err - } else if blocked { - l.Trace("a block exists between requesting account and boosted account") - return false, nil - } - } - - // status boosts a reply to account id - if relevantAccounts.BoostedInReplyToAccount != nil { - if blocked, err := f.db.IsBlocked(ctx, relevantAccounts.BoostedInReplyToAccount.ID, requestingAccount.ID, true); err != nil { - return false, err - } else if blocked { - l.Trace("a block exists between requesting account and boosted reply to account") - return false, nil - } - } - - // boost mentions accounts - for _, a := range relevantAccounts.BoostedMentionedAccounts { - if a == nil { - continue - } - if blocked, err := f.db.IsBlocked(ctx, a.ID, requestingAccount.ID, true); err != nil { - return false, err - } else if blocked { - l.Trace("a block exists between requesting account and a boosted mentioned account") - return false, nil - } - } - - // Iterate mentions to check for blocks or requester mentions - isMentioned, blockAmongMentions := false, false - for _, a := range relevantAccounts.MentionedAccounts { - if a == nil { - continue - } - - if blocked, err := f.db.IsBlocked(ctx, a.ID, requestingAccount.ID, true); err != nil { - return false, err - } else if blocked { - blockAmongMentions = true - break - } - - if a.ID == requestingAccount.ID { - isMentioned = true - } - } - - if blockAmongMentions { - l.Trace("a block exists between requesting account and a mentioned account") - return false, nil - } else if isMentioned { - // Requester mentioned, should always be visible - return true, nil - } - - // at this point we know neither account blocks the other, or another account mentioned or otherwise referred to in the status - // that means it's now just a matter of checking the visibility settings of the status itself - switch targetStatus.Visibility { - case gtsmodel.VisibilityPublic, gtsmodel.VisibilityUnlocked: - // no problem here - case gtsmodel.VisibilityFollowersOnly: - // Followers-only post, check for a one-way follow to target - follows, err := f.db.IsFollowing(ctx, requestingAccount, targetAccount) - if err != nil { - return false, err - } - if !follows { - l.Trace("requested status is followers only but requesting account is not a follower") - return false, nil - } - case gtsmodel.VisibilityMutualsOnly: - // Mutuals-only post, check for a mutual follow - mutuals, err := f.db.IsMutualFollowing(ctx, requestingAccount, targetAccount) - if err != nil { - return false, err - } - if !mutuals { - l.Trace("requested status is mutuals only but accounts aren't mufos") - return false, nil - } - case gtsmodel.VisibilityDirect: - l.Trace("requesting account requests a direct status it's not mentioned in") - return false, nil // it's not mentioned -_- - } - - // If we reached here, all is okay - return true, nil -} - -func (f *filter) StatusesVisible(ctx context.Context, statuses []*gtsmodel.Status, requestingAccount *gtsmodel.Account) ([]*gtsmodel.Status, error) { - filtered := []*gtsmodel.Status{} - for _, s := range statuses { - visible, err := f.StatusVisible(ctx, s, requestingAccount) - if err != nil { - return nil, err - } - if visible { - filtered = append(filtered, s) - } - } - return filtered, nil -} |