diff options
author | 2023-03-20 19:10:08 +0100 | |
---|---|---|
committer | 2023-03-20 18:10:08 +0000 | |
commit | e8595f0c64f527af0913d1a426b697e67ff74ac9 (patch) | |
tree | a5d45b1ad8b96318944408a23fda91f008643900 /internal/db/bundb/relationship.go | |
parent | [chore]: Bump github.com/miekg/dns from 1.1.51 to 1.1.52 (#1636) (diff) | |
download | gotosocial-e8595f0c64f527af0913d1a426b697e67ff74ac9.tar.xz |
[chore] Refactor account deleting/block logic, tidy up some other processing things (#1599)
* start refactoring account deletion
* update to use state.DB
* further messing about
* some more tidying up
* more tidying, cleaning, nice-making
* further adventures in refactoring and the woes of technical debt
* update fr accept/reject
* poking + prodding
* fix up deleting
* create fave uri
* don't log using requestingAccount.ID because it might be nil
* move getBookmarks function
* use exists query to check for status bookmark
* use deletenotifications func
* fiddle
* delete follow request notif
* split up some db functions
* Fix possible nil pointer panic
* fix more possible nil pointers
* fix license headers
* warn when follow missing (target) account
* return wrapped err when bookmark/fave models can't be retrieved
* simplify self account delete
* warn log likely race condition
* de-sillify status delete loop
* move error check due north
* warn when unfollowSideEffects has no target account
* warn when no boost account is found
* warn + dump follow when no account
* more warnings
* warn on fave account not set
* move for loop inside anonymous function
* fix funky logic
* don't remove mutual account items on block;
do make sure unfollow occurs in both directions!
Diffstat (limited to 'internal/db/bundb/relationship.go')
-rw-r--r-- | internal/db/bundb/relationship.go | 389 |
1 files changed, 276 insertions, 113 deletions
diff --git a/internal/db/bundb/relationship.go b/internal/db/bundb/relationship.go index 90f9cebe7..21a29b5dc 100644 --- a/internal/db/bundb/relationship.go +++ b/internal/db/bundb/relationship.go @@ -19,12 +19,12 @@ package bundb import ( "context" - "database/sql" "errors" "fmt" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/state" "github.com/uptrace/bun" ) @@ -34,14 +34,6 @@ type relationshipDB struct { state *state.State } -func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery { - return r.conn. - NewSelect(). - Model(follow). - Relation("Account"). - Relation("TargetAccount") -} - func (r *relationshipDB) IsBlocked(ctx context.Context, account1 string, account2 string, eitherDirection bool) (bool, db.Error) { // Look for a block in direction of account1->account2 block1, err := r.getBlock(ctx, account1, account2) @@ -305,169 +297,340 @@ func (r *relationshipDB) IsMutualFollowing(ctx context.Context, account1 *gtsmod } func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, originAccountID string, targetAccountID string) (*gtsmodel.Follow, db.Error) { - var follow *gtsmodel.Follow - - if err := r.conn.RunInTx(ctx, func(tx bun.Tx) error { - // get original follow request - followRequest := >smodel.FollowRequest{} - if err := tx. - NewSelect(). - Model(followRequest). - Where("? = ?", bun.Ident("follow_request.account_id"), originAccountID). - Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID). - Scan(ctx); err != nil { - return err - } + // Get original follow request. + var followRequestID string + if err := r.conn. + NewSelect(). + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Column("follow_request.id"). + Where("? = ?", bun.Ident("follow_request.account_id"), originAccountID). + Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID). + Scan(ctx, &followRequestID); err != nil { + return nil, r.conn.ProcessError(err) + } - // create a new follow to 'replace' the request with - follow = >smodel.Follow{ - ID: followRequest.ID, - AccountID: originAccountID, - TargetAccountID: targetAccountID, - URI: followRequest.URI, - } + followRequest, err := r.getFollowRequest(ctx, followRequestID) + if err != nil { + return nil, r.conn.ProcessError(err) + } - // if the follow already exists, just update the URI -- we don't need to do anything else - if _, err := tx. - NewInsert(). - Model(follow). - On("CONFLICT (?,?) DO UPDATE set ? = ?", bun.Ident("account_id"), bun.Ident("target_account_id"), bun.Ident("uri"), follow.URI). - Exec(ctx); err != nil { - return err - } + // Create a new follow to 'replace' + // the original follow request with. + follow := >smodel.Follow{ + ID: followRequest.ID, + AccountID: originAccountID, + Account: followRequest.Account, + TargetAccountID: targetAccountID, + TargetAccount: followRequest.TargetAccount, + URI: followRequest.URI, + } - // now remove the follow request - if _, err := tx. - NewDelete(). - TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). - Where("? = ?", bun.Ident("follow_request.id"), followRequest.ID). - Exec(ctx); err != nil { - return err - } + // If the follow already exists, just + // replace the URI with the new one. + if _, err := r.conn. + NewInsert(). + Model(follow). + On("CONFLICT (?,?) DO UPDATE set ? = ?", bun.Ident("account_id"), bun.Ident("target_account_id"), bun.Ident("uri"), follow.URI). + Exec(ctx); err != nil { + return nil, r.conn.ProcessError(err) + } - return nil - }); err != nil { + // Delete original follow request. + if _, err := r.conn. + NewDelete(). + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Where("? = ?", bun.Ident("follow_request.id"), followRequest.ID). + Exec(ctx); err != nil { return nil, r.conn.ProcessError(err) } + // Delete original follow request notification. + if err := r.deleteFollowRequestNotif(ctx, originAccountID, targetAccountID); err != nil { + return nil, err + } + // return the new follow return follow, nil } func (r *relationshipDB) RejectFollowRequest(ctx context.Context, originAccountID string, targetAccountID string) (*gtsmodel.FollowRequest, db.Error) { - followRequest := >smodel.FollowRequest{} - - if err := r.conn.RunInTx(ctx, func(tx bun.Tx) error { - // get original follow request - if err := tx. - NewSelect(). - Model(followRequest). - Where("? = ?", bun.Ident("follow_request.account_id"), originAccountID). - Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID). - Scan(ctx); err != nil { - return err - } + // Get original follow request. + var followRequestID string + if err := r.conn. + NewSelect(). + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Column("follow_request.id"). + Where("? = ?", bun.Ident("follow_request.account_id"), originAccountID). + Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID). + Scan(ctx, &followRequestID); err != nil { + return nil, r.conn.ProcessError(err) + } - // now delete it from the database by ID - if _, err := tx. - NewDelete(). - TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). - Where("? = ?", bun.Ident("follow_request.id"), followRequest.ID). - Exec(ctx); err != nil { - return err - } + followRequest, err := r.getFollowRequest(ctx, followRequestID) + if err != nil { + return nil, r.conn.ProcessError(err) + } - return nil - }); err != nil { + // Delete original follow request. + if _, err := r.conn. + NewDelete(). + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Where("? = ?", bun.Ident("follow_request.id"), followRequest.ID). + Exec(ctx); err != nil { return nil, r.conn.ProcessError(err) } - // return the deleted follow request + // Delete original follow request notification. + if err := r.deleteFollowRequestNotif(ctx, originAccountID, targetAccountID); err != nil { + return nil, err + } + + // Return the now deleted follow request. return followRequest, nil } -func (r *relationshipDB) GetAccountFollowRequests(ctx context.Context, accountID string) ([]*gtsmodel.FollowRequest, db.Error) { - followRequests := []*gtsmodel.FollowRequest{} +func (r *relationshipDB) deleteFollowRequestNotif(ctx context.Context, originAccountID string, targetAccountID string) db.Error { + var id string + if err := r.conn. + NewSelect(). + TableExpr("? AS ?", bun.Ident("notifications"), bun.Ident("notification")). + Column("notification.id"). + Where("? = ?", bun.Ident("notification.origin_account_id"), originAccountID). + Where("? = ?", bun.Ident("notification.target_account_id"), targetAccountID). + Where("? = ?", bun.Ident("notification.notification_type"), gtsmodel.NotificationFollowRequest). + Limit(1). // There should only be one! + Scan(ctx, &id); err != nil { + err = r.conn.ProcessError(err) + if errors.Is(err, db.ErrNoEntries) { + // If no entries, the notif didn't + // exist anyway so nothing to do here. + return nil + } + // Return on real error. + return err + } - q := r.newFollowQ(&followRequests). - Where("? = ?", bun.Ident("follow_request.target_account_id"), accountID). - Order("follow_request.updated_at DESC") + return r.state.DB.DeleteNotification(ctx, id) +} - if err := q.Scan(ctx); err != nil { +func (r *relationshipDB) getFollow(ctx context.Context, id string) (*gtsmodel.Follow, db.Error) { + follow := >smodel.Follow{} + + err := r.conn. + NewSelect(). + Model(follow). + Where("? = ?", bun.Ident("follow.id"), id). + Scan(ctx) + if err != nil { return nil, r.conn.ProcessError(err) } - return followRequests, nil + follow.Account, err = r.state.DB.GetAccountByID(ctx, follow.AccountID) + if err != nil { + log.Errorf(ctx, "error getting follow account %q: %v", follow.AccountID, err) + } + + follow.TargetAccount, err = r.state.DB.GetAccountByID(ctx, follow.TargetAccountID) + if err != nil { + log.Errorf(ctx, "error getting follow target account %q: %v", follow.TargetAccountID, err) + } + + return follow, nil +} + +func (r *relationshipDB) GetLocalFollowersIDs(ctx context.Context, targetAccountID string) ([]string, db.Error) { + accountIDs := []string{} + + // Select only the account ID of each follow. + q := r.conn. + NewSelect(). + TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")). + ColumnExpr("? AS ?", bun.Ident("follow.account_id"), bun.Ident("account_id")). + Where("? = ?", bun.Ident("follow.target_account_id"), targetAccountID) + + // Join on accounts table to select only + // those with NULL domain (local accounts). + q = q. + Join("JOIN ? AS ? ON ? = ?", + bun.Ident("accounts"), + bun.Ident("account"), + bun.Ident("follow.account_id"), + bun.Ident("account.id"), + ). + Where("? IS NULL", bun.Ident("account.domain")) + + // We don't *really* need to order these, + // but it makes it more consistent to do so. + q = q.Order("account_id DESC") + + if err := q.Scan(ctx, &accountIDs); err != nil { + return nil, r.conn.ProcessError(err) + } + + return accountIDs, nil } -func (r *relationshipDB) GetAccountFollows(ctx context.Context, accountID string) ([]*gtsmodel.Follow, db.Error) { - follows := []*gtsmodel.Follow{} +func (r *relationshipDB) GetFollows(ctx context.Context, accountID string, targetAccountID string) ([]*gtsmodel.Follow, db.Error) { + ids := []string{} - q := r.newFollowQ(&follows). - Where("? = ?", bun.Ident("follow.account_id"), accountID). + q := r.conn. + NewSelect(). + TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")). + Column("follow.id"). Order("follow.updated_at DESC") - if err := q.Scan(ctx); err != nil { + if accountID != "" { + q = q.Where("? = ?", bun.Ident("follow.account_id"), accountID) + } + + if targetAccountID != "" { + q = q.Where("? = ?", bun.Ident("follow.target_account_id"), targetAccountID) + } + + if err := q.Scan(ctx, &ids); err != nil { return nil, r.conn.ProcessError(err) } + follows := make([]*gtsmodel.Follow, 0, len(ids)) + for _, id := range ids { + follow, err := r.getFollow(ctx, id) + if err != nil { + log.Errorf(ctx, "error getting follow %q: %v", id, err) + continue + } + + follows = append(follows, follow) + } + return follows, nil } -func (r *relationshipDB) CountAccountFollows(ctx context.Context, accountID string, localOnly bool) (int, db.Error) { +func (r *relationshipDB) CountFollows(ctx context.Context, accountID string, targetAccountID string) (int, db.Error) { q := r.conn. NewSelect(). - TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")) + TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")). + Column("follow.id") - if localOnly { - q = q. - Join("JOIN ? AS ? ON ? = ?", bun.Ident("accounts"), bun.Ident("account"), bun.Ident("follow.target_account_id"), bun.Ident("account.id")). - Where("? = ?", bun.Ident("follow.account_id"), accountID). - Where("? IS NULL", bun.Ident("account.domain")) - } else { + if accountID != "" { q = q.Where("? = ?", bun.Ident("follow.account_id"), accountID) } + if targetAccountID != "" { + q = q.Where("? = ?", bun.Ident("follow.target_account_id"), targetAccountID) + } + return q.Count(ctx) } -func (r *relationshipDB) GetAccountFollowedBy(ctx context.Context, accountID string, localOnly bool) ([]*gtsmodel.Follow, db.Error) { - follows := []*gtsmodel.Follow{} +func (r *relationshipDB) getFollowRequest(ctx context.Context, id string) (*gtsmodel.FollowRequest, db.Error) { + followRequest := >smodel.FollowRequest{} + + err := r.conn. + NewSelect(). + Model(followRequest). + Where("? = ?", bun.Ident("follow_request.id"), id). + Scan(ctx) + if err != nil { + return nil, r.conn.ProcessError(err) + } + + followRequest.Account, err = r.state.DB.GetAccountByID(ctx, followRequest.AccountID) + if err != nil { + log.Errorf(ctx, "error getting follow request account %q: %v", followRequest.AccountID, err) + } + + followRequest.TargetAccount, err = r.state.DB.GetAccountByID(ctx, followRequest.TargetAccountID) + if err != nil { + log.Errorf(ctx, "error getting follow request target account %q: %v", followRequest.TargetAccountID, err) + } + + return followRequest, nil +} + +func (r *relationshipDB) GetFollowRequests(ctx context.Context, accountID string, targetAccountID string) ([]*gtsmodel.FollowRequest, db.Error) { + ids := []string{} q := r.conn. NewSelect(). - Model(&follows). - Order("follow.updated_at DESC") + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Column("follow_request.id") - if localOnly { - q = q. - Join("JOIN ? AS ? ON ? = ?", bun.Ident("accounts"), bun.Ident("account"), bun.Ident("follow.account_id"), bun.Ident("account.id")). - Where("? = ?", bun.Ident("follow.target_account_id"), accountID). - Where("? IS NULL", bun.Ident("account.domain")) - } else { - q = q.Where("? = ?", bun.Ident("follow.target_account_id"), accountID) + if accountID != "" { + q = q.Where("? = ?", bun.Ident("follow_request.account_id"), accountID) + } + + if targetAccountID != "" { + q = q.Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID) } - err := q.Scan(ctx) - if err != nil && err != sql.ErrNoRows { + if err := q.Scan(ctx, &ids); err != nil { return nil, r.conn.ProcessError(err) } - return follows, nil + + followRequests := make([]*gtsmodel.FollowRequest, 0, len(ids)) + for _, id := range ids { + followRequest, err := r.getFollowRequest(ctx, id) + if err != nil { + log.Errorf(ctx, "error getting follow request %q: %v", id, err) + continue + } + + followRequests = append(followRequests, followRequest) + } + + return followRequests, nil } -func (r *relationshipDB) CountAccountFollowedBy(ctx context.Context, accountID string, localOnly bool) (int, db.Error) { +func (r *relationshipDB) CountFollowRequests(ctx context.Context, accountID string, targetAccountID string) (int, db.Error) { q := r.conn. NewSelect(). - TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")) + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Column("follow_request.id"). + Order("follow_request.updated_at DESC") - if localOnly { - q = q. - Join("JOIN ? AS ? ON ? = ?", bun.Ident("accounts"), bun.Ident("account"), bun.Ident("follow.account_id"), bun.Ident("account.id")). - Where("? = ?", bun.Ident("follow.target_account_id"), accountID). - Where("? IS NULL", bun.Ident("account.domain")) - } else { - q = q.Where("? = ?", bun.Ident("follow.target_account_id"), accountID) + if accountID != "" { + q = q.Where("? = ?", bun.Ident("follow_request.account_id"), accountID) + } + + if targetAccountID != "" { + q = q.Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID) } return q.Count(ctx) } + +func (r *relationshipDB) Unfollow(ctx context.Context, originAccountID string, targetAccountID string) (string, db.Error) { + uri := new(string) + + _, err := r.conn. + NewDelete(). + TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")). + Where("? = ?", bun.Ident("follow.target_account_id"), targetAccountID). + Where("? = ?", bun.Ident("follow.account_id"), originAccountID). + Returning("?", bun.Ident("uri")).Exec(ctx, uri) + + // Only return proper errors. + if err = r.conn.ProcessError(err); err != db.ErrNoEntries { + return *uri, err + } + + return *uri, nil +} + +func (r *relationshipDB) UnfollowRequest(ctx context.Context, originAccountID string, targetAccountID string) (string, db.Error) { + uri := new(string) + + _, err := r.conn. + NewDelete(). + TableExpr("? AS ?", bun.Ident("follow_requests"), bun.Ident("follow_request")). + Where("? = ?", bun.Ident("follow_request.target_account_id"), targetAccountID). + Where("? = ?", bun.Ident("follow_request.account_id"), originAccountID). + Returning("?", bun.Ident("uri")).Exec(ctx, uri) + + // Only return proper errors. + if err = r.conn.ProcessError(err); err != db.ErrNoEntries { + return *uri, err + } + + return *uri, nil +} |