diff --git a/internal/processing/account/follow.go b/internal/processing/account/follow.go index 402c764e8..6c066d6a6 100644 --- a/internal/processing/account/follow.go +++ b/internal/processing/account/follow.go @@ -30,6 +30,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/uris" + "github.com/superseriousbusiness/gotosocial/internal/util" ) // FollowCreate handles a follow request to an account, either remote or local. @@ -98,35 +99,38 @@ func (p *Processor) FollowCreate(ctx context.Context, requestingAccount *gtsmode Notify: form.Notify, } + // Insert the new follow request. if err := p.state.DB.PutFollowRequest(ctx, fr); err != nil { err = gtserror.Newf("error creating follow request in db: %s", err) return nil, gtserror.NewErrorInternalError(err) } - if targetAccount.IsLocal() && !*targetAccount.Locked { - // If the target account is local and not locked, - // we can already accept the follow request and - // skip any further processing. - // - // Because we know the requestingAccount is also - // local, we don't need to federate the accept out. - if _, err := p.state.DB.AcceptFollowRequest(ctx, requestingAccount.ID, form.ID); err != nil { - err = gtserror.Newf("error accepting follow request for local unlocked account: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } - } else { - // Otherwise we leave the follow request as it is, - // and we handle the rest of the process async. - p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ - APObjectType: ap.ActivityFollow, - APActivityType: ap.ActivityCreate, - GTSModel: fr, - Origin: requestingAccount, - Target: targetAccount, - }) + // And get the new relationship state. + rel, errWithCode := p.RelationshipGet(ctx, requestingAccount, form.ID) + if errWithCode != nil { + return nil, errWithCode } - return p.RelationshipGet(ctx, requestingAccount, form.ID) + // For unlocked accounts on the same instance, + // we can already optimistically show the follow + // request as accepted in the returned relationship. + if targetAccount.IsLocal() && !*targetAccount.Locked { + rel.Requested = false + rel.Following = true + rel.ShowingReblogs = util.PtrValueOr(fr.ShowReblogs, true) + rel.Notifying = util.PtrValueOr(fr.Notify, false) + } + + // Handle side effects async. + p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityCreate, + GTSModel: fr, + Origin: requestingAccount, + Target: targetAccount, + }) + + return rel, nil } // FollowRemove handles the removal of a follow/follow request to an account, either remote or local. diff --git a/internal/processing/workers/fromclientapi.go b/internal/processing/workers/fromclientapi.go index 89b8f546f..d5d4265e1 100644 --- a/internal/processing/workers/fromclientapi.go +++ b/internal/processing/workers/fromclientapi.go @@ -305,6 +305,30 @@ func (p *clientAPI) CreateFollowReq(ctx context.Context, cMsg *messages.FromClie return gtserror.Newf("%T not parseable as *gtsmodel.FollowRequest", cMsg.GTSModel) } + // If target is a local, unlocked account, + // we can skip side effects for the follow + // request and accept the follow immediately. + if cMsg.Target.IsLocal() && !*cMsg.Target.Locked { + // Accept the FR first to get the Follow. + follow, err := p.state.DB.AcceptFollowRequest( + ctx, + cMsg.Origin.ID, + cMsg.Target.ID, + ) + if err != nil { + return gtserror.Newf("db error accepting follow req: %w", err) + } + + // Use AcceptFollow to do side effects. + return p.AcceptFollow(ctx, &messages.FromClientAPI{ + APObjectType: ap.ActivityFollow, + APActivityType: ap.ActivityAccept, + GTSModel: follow, + Origin: cMsg.Origin, + Target: cMsg.Target, + }) + } + // Update stats for the target account. if err := p.utils.incrementFollowRequestsCount(ctx, cMsg.Target); err != nil { log.Errorf(ctx, "error updating account stats: %v", err)