Skip to content

Commit

Permalink
[chore] Make paging logic more generic (#901)
Browse files Browse the repository at this point in the history
* make paging logic more generic
not just for timelines!

* linty linterson
  • Loading branch information
tsmethurst committed Oct 10, 2022
1 parent 8066306 commit 832befd
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 200 deletions.
10 changes: 4 additions & 6 deletions internal/api/model/timeline.go → internal/api/model/paging.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@

package model

import "github.com/superseriousbusiness/gotosocial/internal/timeline"

// TimelineResponse wraps a slice of timelineables, ready to be serialized, along with the Link
// header for the previous and next queries, to be returned to the client.
type TimelineResponse struct {
Items []timeline.Timelineable
// PageableResponse wraps a slice of items, ready to be serialized, along with the Link
// header for the previous and next queries / pages, to be returned to the client.
type PageableResponse struct {
Items []interface{}
LinkHeader string
NextLink string
PrevLink string
Expand Down
4 changes: 2 additions & 2 deletions internal/processing/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form
return p.accountProcessor.Update(ctx, authed.Account, form)
}

func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.TimelineResponse, gtserror.WithCode) {
func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) {
return p.accountProcessor.StatusesGet(ctx, authed.Account, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
}

func (p *processor) AccountWebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.TimelineResponse, gtserror.WithCode) {
func (p *processor) AccountWebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) {
return p.accountProcessor.WebStatusesGet(ctx, targetAccountID, maxID)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/processing/account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ type Processor interface {
Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
// the account given in authed.
StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.TimelineResponse, gtserror.WithCode)
StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode)
// WebStatusesGet fetches a number of statuses (in descending order) from the given account. It selects only
// statuses which are suitable for showing on the public web profile of an account.
WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.TimelineResponse, gtserror.WithCode)
WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode)
// FollowersGet fetches a list of the target account's followers.
FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
// FollowingGet fetches a list of the accounts that target account is following.
Expand Down
73 changes: 50 additions & 23 deletions internal/processing/account/getstatuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/timeline"
"github.com/superseriousbusiness/gotosocial/internal/util"
)

func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.TimelineResponse, gtserror.WithCode) {
func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode) {
if requestingAccount != nil {
if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil {
return nil, gtserror.NewErrorInternalError(err)
Expand All @@ -42,7 +41,7 @@ func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel
statuses, err := p.db.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
if err != nil {
if err == db.ErrNoEntries {
return util.EmptyTimelineResponse(), nil
return util.EmptyPageableResponse(), nil
}
return nil, gtserror.NewErrorInternalError(err)
}
Expand All @@ -55,25 +54,37 @@ func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel
}
}

if len(filtered) == 0 {
return util.EmptyTimelineResponse(), nil
count := len(filtered)

if count == 0 {
return util.EmptyPageableResponse(), nil
}

timelineables := []timeline.Timelineable{}
for _, i := range filtered {
apiStatus, err := p.tc.StatusToAPIStatus(ctx, i, requestingAccount)
items := []interface{}{}
nextMaxIDValue := ""
prevMinIDValue := ""
for i, s := range filtered {
item, err := p.tc.StatusToAPIStatus(ctx, s, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to api: %s", err))
}

timelineables = append(timelineables, apiStatus)
if i == count-1 {
nextMaxIDValue = item.GetID()
}

if i == 0 {
prevMinIDValue = item.GetID()
}

items = append(items, item)
}

return util.PackageTimelineableResponse(util.TimelineableResponseParams{
Items: timelineables,
return util.PackagePageableResponse(util.PageableResponseParams{
Items: items,
Path: fmt.Sprintf("/api/v1/accounts/%s/statuses", targetAccountID),
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
PrevMinIDValue: timelineables[0].GetID(),
NextMaxIDValue: nextMaxIDValue,
PrevMinIDValue: prevMinIDValue,
Limit: limit,
ExtraQueryParams: []string{
fmt.Sprintf("exclude_replies=%t", excludeReplies),
Expand All @@ -85,7 +96,7 @@ func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel
})
}

func (p *processor) WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.TimelineResponse, gtserror.WithCode) {
func (p *processor) WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) {
acct, err := p.db.GetAccountByID(ctx, targetAccountID)
if err != nil {
if err == db.ErrNoEntries {
Expand All @@ -103,26 +114,42 @@ func (p *processor) WebStatusesGet(ctx context.Context, targetAccountID string,
statuses, err := p.db.GetAccountWebStatuses(ctx, targetAccountID, 10, maxID)
if err != nil {
if err == db.ErrNoEntries {
return util.EmptyTimelineResponse(), nil
return util.EmptyPageableResponse(), nil
}
return nil, gtserror.NewErrorInternalError(err)
}

timelineables := []timeline.Timelineable{}
for _, i := range statuses {
apiStatus, err := p.tc.StatusToAPIStatus(ctx, i, nil)
count := len(statuses)

if count == 0 {
return util.EmptyPageableResponse(), nil
}

items := []interface{}{}
nextMaxIDValue := ""
prevMinIDValue := ""
for i, s := range statuses {
item, err := p.tc.StatusToAPIStatus(ctx, s, nil)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to api: %s", err))
}

timelineables = append(timelineables, apiStatus)
if i == count-1 {
nextMaxIDValue = item.GetID()
}

if i == 0 {
prevMinIDValue = item.GetID()
}

items = append(items, item)
}

return util.PackageTimelineableResponse(util.TimelineableResponseParams{
Items: timelineables,
return util.PackagePageableResponse(util.PageableResponseParams{
Items: items,
Path: "/@" + acct.Username,
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
PrevMinIDValue: timelineables[0].GetID(),
NextMaxIDValue: nextMaxIDValue,
PrevMinIDValue: prevMinIDValue,
ExtraQueryParams: []string{},
})
}
36 changes: 24 additions & 12 deletions internal/processing/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,48 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/timeline"
"github.com/superseriousbusiness/gotosocial/internal/util"
)

func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode) {
func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.PageableResponse, gtserror.WithCode) {
notifs, err := p.db.GetNotifications(ctx, authed.Account.ID, excludeTypes, limit, maxID, sinceID)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

if len(notifs) == 0 {
return util.EmptyTimelineResponse(), nil
count := len(notifs)

if count == 0 {
return util.EmptyPageableResponse(), nil
}

timelineables := []timeline.Timelineable{}
for _, n := range notifs {
apiNotif, err := p.tc.NotificationToAPINotification(ctx, n)
items := []interface{}{}
nextMaxIDValue := ""
prevMinIDValue := ""
for i, n := range notifs {
item, err := p.tc.NotificationToAPINotification(ctx, n)
if err != nil {
log.Debugf("got an error converting a notification to api, will skip it: %s", err)
continue
}
timelineables = append(timelineables, apiNotif)

if i == count-1 {
nextMaxIDValue = item.GetID()
}

if i == 0 {
prevMinIDValue = item.GetID()
}

items = append(items, item)
}

return util.PackageTimelineableResponse(util.TimelineableResponseParams{
Items: timelineables,
return util.PackagePageableResponse(util.PageableResponseParams{
Items: items,
Path: "api/v1/notifications",
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
NextMaxIDValue: nextMaxIDValue,
PrevMinIDKey: "since_id",
PrevMinIDValue: timelineables[0].GetID(),
PrevMinIDValue: prevMinIDValue,
Limit: limit,
})
}
Expand Down
12 changes: 6 additions & 6 deletions internal/processing/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ type Processor interface {
AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
// the account given in authed.
AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.TimelineResponse, gtserror.WithCode)
AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) (*apimodel.PageableResponse, gtserror.WithCode)
// AccountWebStatusesGet fetches a number of statuses (in descending order) from the given account. It selects only
// statuses which are suitable for showing on the public web profile of an account.
AccountWebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.TimelineResponse, gtserror.WithCode)
AccountWebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode)
// AccountFollowersGet fetches a list of the target account's followers.
AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
// AccountFollowingGet fetches a list of the accounts that target account is following.
Expand Down Expand Up @@ -160,7 +160,7 @@ type Processor interface {
MediaUpdate(ctx context.Context, authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode)

// NotificationsGet
NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode)
NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.PageableResponse, gtserror.WithCode)
// NotificationsClear
NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode

Expand Down Expand Up @@ -192,11 +192,11 @@ type Processor interface {
StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode)

// HomeTimelineGet returns statuses from the home timeline, with the given filters/parameters.
HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.TimelineResponse, gtserror.WithCode)
HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode)
// PublicTimelineGet returns statuses from the public/local timeline, with the given filters/parameters.
PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.TimelineResponse, gtserror.WithCode)
PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode)
// FavedTimelineGet returns faved statuses, with the given filters/parameters.
FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.TimelineResponse, gtserror.WithCode)
FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode)

// AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid.
AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode)
Expand Down

0 comments on commit 832befd

Please sign in to comment.