diff options
author | 2023-09-12 14:00:35 +0100 | |
---|---|---|
committer | 2023-09-12 14:00:35 +0100 | |
commit | 7293d6029b43db693fd170c0c087394339da0677 (patch) | |
tree | 09063243faf1b178fde35973486e311f66b1ca33 /internal/processing/account/relationships.go | |
parent | [feature] Allow admins to expire remote public keys; refetch expired keys on ... (diff) | |
download | gotosocial-7293d6029b43db693fd170c0c087394339da0677.tar.xz |
[feature] add paging to account follows, followers and follow requests endpoints (#2186)
Diffstat (limited to 'internal/processing/account/relationships.go')
-rw-r--r-- | internal/processing/account/relationships.go | 166 |
1 files changed, 79 insertions, 87 deletions
diff --git a/internal/processing/account/relationships.go b/internal/processing/account/relationships.go index d12d989ef..58c98f3ba 100644 --- a/internal/processing/account/relationships.go +++ b/internal/processing/account/relationships.go @@ -20,128 +20,120 @@ package account import ( "context" "errors" - "fmt" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/superseriousbusiness/gotosocial/internal/paging" ) // FollowersGet fetches a list of the target account's followers. -func (p *Processor) FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { - if blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, targetAccountID); err != nil { - err = fmt.Errorf("FollowersGet: db error checking block: %w", err) +func (p *Processor) FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, page *paging.Page) (*apimodel.PageableResponse, gtserror.WithCode) { + // Fetch target account to check it exists, and visibility of requester->target. + _, errWithCode := p.c.GetVisibleTargetAccount(ctx, requestingAccount, targetAccountID) + if errWithCode != nil { + return nil, errWithCode + } + + follows, err := p.state.DB.GetAccountFollowers(ctx, targetAccountID, page) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err = gtserror.Newf("db error getting followers: %w", err) return nil, gtserror.NewErrorInternalError(err) - } else if blocked { - err = errors.New("FollowersGet: block exists between accounts") - return nil, gtserror.NewErrorNotFound(err) } - follows, err := p.state.DB.GetAccountFollowers(ctx, targetAccountID) - if err != nil { - if !errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("FollowersGet: db error getting followers: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } - return []apimodel.Account{}, nil + // Check for empty response. + count := len(follows) + if count == 0 { + return paging.EmptyResponse(), nil } - return p.accountsFromFollows(ctx, follows, requestingAccount.ID) + // Get the lowest and highest + // ID values, used for paging. + lo := follows[count-1].ID + hi := follows[0].ID + + // Func to fetch follow source at index. + getIdx := func(i int) *gtsmodel.Account { + return follows[i].Account + } + + // Get a filtered slice of public API account models. + items := p.c.GetVisibleAPIAccountsPaged(ctx, + requestingAccount, + getIdx, + len(follows), + ) + + return paging.PackageResponse(paging.ResponseParams{ + Items: items, + Path: "/api/v1/accounts/" + targetAccountID + "/followers", + Next: page.Next(lo, hi), + Prev: page.Prev(lo, hi), + }), nil } // FollowingGet fetches a list of the accounts that target account is following. -func (p *Processor) FollowingGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { - if blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccount.ID, targetAccountID); err != nil { - err = fmt.Errorf("FollowingGet: db error checking block: %w", err) +func (p *Processor) FollowingGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, page *paging.Page) (*apimodel.PageableResponse, gtserror.WithCode) { + // Fetch target account to check it exists, and visibility of requester->target. + _, errWithCode := p.c.GetVisibleTargetAccount(ctx, requestingAccount, targetAccountID) + if errWithCode != nil { + return nil, errWithCode + } + + // Fetch known accounts that follow given target account ID. + follows, err := p.state.DB.GetAccountFollows(ctx, targetAccountID, page) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + err = gtserror.Newf("db error getting followers: %w", err) return nil, gtserror.NewErrorInternalError(err) - } else if blocked { - err = errors.New("FollowingGet: block exists between accounts") - return nil, gtserror.NewErrorNotFound(err) } - follows, err := p.state.DB.GetAccountFollows(ctx, targetAccountID) - if err != nil { - if !errors.Is(err, db.ErrNoEntries) { - err = fmt.Errorf("FollowingGet: db error getting followers: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } - return []apimodel.Account{}, nil + // Check for empty response. + count := len(follows) + if count == 0 { + return paging.EmptyResponse(), nil } - return p.targetAccountsFromFollows(ctx, follows, requestingAccount.ID) + // Get the lowest and highest + // ID values, used for paging. + lo := follows[count-1].ID + hi := follows[0].ID + + // Func to fetch follow source at index. + getIdx := func(i int) *gtsmodel.Account { + return follows[i].TargetAccount + } + + // Get a filtered slice of public API account models. + items := p.c.GetVisibleAPIAccountsPaged(ctx, + requestingAccount, + getIdx, + len(follows), + ) + + return paging.PackageResponse(paging.ResponseParams{ + Items: items, + Path: "/api/v1/accounts/" + targetAccountID + "/following", + Next: page.Next(lo, hi), + Prev: page.Prev(lo, hi), + }), nil } // RelationshipGet returns a relationship model describing the relationship of the targetAccount to the Authed account. func (p *Processor) RelationshipGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { if requestingAccount == nil { - return nil, gtserror.NewErrorForbidden(errors.New("not authed")) + return nil, gtserror.NewErrorForbidden(gtserror.New("not authed")) } gtsR, err := p.state.DB.GetRelationship(ctx, requestingAccount.ID, targetAccountID) if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err)) + return nil, gtserror.NewErrorInternalError(gtserror.Newf("error getting relationship: %s", err)) } r, err := p.tc.RelationshipToAPIRelationship(ctx, gtsR) if err != nil { - return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err)) + return nil, gtserror.NewErrorInternalError(gtserror.Newf("error converting relationship: %s", err)) } return r, nil } - -func (p *Processor) accountsFromFollows(ctx context.Context, follows []*gtsmodel.Follow, requestingAccountID string) ([]apimodel.Account, gtserror.WithCode) { - accounts := make([]apimodel.Account, 0, len(follows)) - for _, follow := range follows { - if follow.Account == nil { - // No account set for some reason; just skip. - log.WithContext(ctx).WithField("follow", follow).Warn("follow had no associated account") - continue - } - - if blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccountID, follow.AccountID); err != nil { - err = fmt.Errorf("accountsFromFollows: db error checking block: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } else if blocked { - continue - } - - account, err := p.tc.AccountToAPIAccountPublic(ctx, follow.Account) - if err != nil { - err = fmt.Errorf("accountsFromFollows: error converting account to api account: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } - - accounts = append(accounts, *account) - } - return accounts, nil -} - -func (p *Processor) targetAccountsFromFollows(ctx context.Context, follows []*gtsmodel.Follow, requestingAccountID string) ([]apimodel.Account, gtserror.WithCode) { - accounts := make([]apimodel.Account, 0, len(follows)) - for _, follow := range follows { - if follow.TargetAccount == nil { - // No account set for some reason; just skip. - log.WithContext(ctx).WithField("follow", follow).Warn("follow had no associated target account") - continue - } - - if blocked, err := p.state.DB.IsEitherBlocked(ctx, requestingAccountID, follow.TargetAccountID); err != nil { - err = fmt.Errorf("targetAccountsFromFollows: db error checking block: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } else if blocked { - continue - } - - account, err := p.tc.AccountToAPIAccountPublic(ctx, follow.TargetAccount) - if err != nil { - err = fmt.Errorf("targetAccountsFromFollows: error converting account to api account: %w", err) - return nil, gtserror.NewErrorInternalError(err) - } - - accounts = append(accounts, *account) - } - return accounts, nil -} |