summaryrefslogtreecommitdiff
path: root/internal/db/bundb/relationship.go
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2023-03-20 19:10:08 +0100
committerLibravatar GitHub <noreply@github.com>2023-03-20 18:10:08 +0000
commite8595f0c64f527af0913d1a426b697e67ff74ac9 (patch)
treea5d45b1ad8b96318944408a23fda91f008643900 /internal/db/bundb/relationship.go
parent[chore]: Bump github.com/miekg/dns from 1.1.51 to 1.1.52 (#1636) (diff)
downloadgotosocial-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.go389
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 := &gtsmodel.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 = &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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 := &gtsmodel.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
+}