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 +}  | 
