mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-25 21:26:40 +00:00
[chore] Make paging logic more generic (#901)
* make paging logic more generic not just for timelines! * linty linterson
This commit is contained in:
parent
80663061d8
commit
832befd727
|
@ -18,12 +18,10 @@
|
||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import "github.com/superseriousbusiness/gotosocial/internal/timeline"
|
// 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.
|
||||||
// TimelineResponse wraps a slice of timelineables, ready to be serialized, along with the Link
|
type PageableResponse struct {
|
||||||
// header for the previous and next queries, to be returned to the client.
|
Items []interface{}
|
||||||
type TimelineResponse struct {
|
|
||||||
Items []timeline.Timelineable
|
|
||||||
LinkHeader string
|
LinkHeader string
|
||||||
NextLink string
|
NextLink string
|
||||||
PrevLink string
|
PrevLink string
|
|
@ -55,11 +55,11 @@ func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form
|
||||||
return p.accountProcessor.Update(ctx, authed.Account, 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)
|
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)
|
return p.accountProcessor.WebStatusesGet(ctx, targetAccountID, maxID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,10 +60,10 @@ type Processor interface {
|
||||||
Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
|
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
|
// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
|
||||||
// the account given in authed.
|
// 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
|
// 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.
|
// 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 fetches a list of the target account's followers.
|
||||||
FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
|
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.
|
// FollowingGet fetches a list of the accounts that target account is following.
|
||||||
|
|
|
@ -26,11 +26,10 @@
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"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 requestingAccount != nil {
|
||||||
if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil {
|
if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
@ -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)
|
statuses, err := p.db.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
return util.EmptyTimelineResponse(), nil
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
@ -55,25 +54,37 @@ func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(filtered) == 0 {
|
count := len(filtered)
|
||||||
return util.EmptyTimelineResponse(), nil
|
|
||||||
|
if count == 0 {
|
||||||
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineables := []timeline.Timelineable{}
|
items := []interface{}{}
|
||||||
for _, i := range filtered {
|
nextMaxIDValue := ""
|
||||||
apiStatus, err := p.tc.StatusToAPIStatus(ctx, i, requestingAccount)
|
prevMinIDValue := ""
|
||||||
|
for i, s := range filtered {
|
||||||
|
item, err := p.tc.StatusToAPIStatus(ctx, s, requestingAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to api: %s", err))
|
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{
|
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||||
Items: timelineables,
|
Items: items,
|
||||||
Path: fmt.Sprintf("/api/v1/accounts/%s/statuses", targetAccountID),
|
Path: fmt.Sprintf("/api/v1/accounts/%s/statuses", targetAccountID),
|
||||||
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
|
NextMaxIDValue: nextMaxIDValue,
|
||||||
PrevMinIDValue: timelineables[0].GetID(),
|
PrevMinIDValue: prevMinIDValue,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
ExtraQueryParams: []string{
|
ExtraQueryParams: []string{
|
||||||
fmt.Sprintf("exclude_replies=%t", excludeReplies),
|
fmt.Sprintf("exclude_replies=%t", excludeReplies),
|
||||||
|
@ -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)
|
acct, err := p.db.GetAccountByID(ctx, targetAccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
|
@ -103,26 +114,42 @@ func (p *processor) WebStatusesGet(ctx context.Context, targetAccountID string,
|
||||||
statuses, err := p.db.GetAccountWebStatuses(ctx, targetAccountID, 10, maxID)
|
statuses, err := p.db.GetAccountWebStatuses(ctx, targetAccountID, 10, maxID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
return util.EmptyTimelineResponse(), nil
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineables := []timeline.Timelineable{}
|
count := len(statuses)
|
||||||
for _, i := range statuses {
|
|
||||||
apiStatus, err := p.tc.StatusToAPIStatus(ctx, i, nil)
|
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 {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to api: %s", err))
|
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{
|
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||||
Items: timelineables,
|
Items: items,
|
||||||
Path: "/@" + acct.Username,
|
Path: "/@" + acct.Username,
|
||||||
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
|
NextMaxIDValue: nextMaxIDValue,
|
||||||
PrevMinIDValue: timelineables[0].GetID(),
|
PrevMinIDValue: prevMinIDValue,
|
||||||
ExtraQueryParams: []string{},
|
ExtraQueryParams: []string{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,36 +25,48 @@
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"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)
|
notifs, err := p.db.GetNotifications(ctx, authed.Account.ID, excludeTypes, limit, maxID, sinceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(notifs) == 0 {
|
count := len(notifs)
|
||||||
return util.EmptyTimelineResponse(), nil
|
|
||||||
|
if count == 0 {
|
||||||
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineables := []timeline.Timelineable{}
|
items := []interface{}{}
|
||||||
for _, n := range notifs {
|
nextMaxIDValue := ""
|
||||||
apiNotif, err := p.tc.NotificationToAPINotification(ctx, n)
|
prevMinIDValue := ""
|
||||||
|
for i, n := range notifs {
|
||||||
|
item, err := p.tc.NotificationToAPINotification(ctx, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("got an error converting a notification to api, will skip it: %s", err)
|
log.Debugf("got an error converting a notification to api, will skip it: %s", err)
|
||||||
continue
|
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{
|
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||||
Items: timelineables,
|
Items: items,
|
||||||
Path: "api/v1/notifications",
|
Path: "api/v1/notifications",
|
||||||
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
|
NextMaxIDValue: nextMaxIDValue,
|
||||||
PrevMinIDKey: "since_id",
|
PrevMinIDKey: "since_id",
|
||||||
PrevMinIDValue: timelineables[0].GetID(),
|
PrevMinIDValue: prevMinIDValue,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,10 +89,10 @@ type Processor interface {
|
||||||
AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, gtserror.WithCode)
|
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
|
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
|
||||||
// the account given in authed.
|
// 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
|
// 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.
|
// 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 fetches a list of the target account's followers.
|
||||||
AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)
|
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.
|
// AccountFollowingGet fetches a list of the accounts that target account is following.
|
||||||
|
@ -160,7 +160,7 @@ type Processor interface {
|
||||||
MediaUpdate(ctx context.Context, authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode)
|
MediaUpdate(ctx context.Context, authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode)
|
||||||
|
|
||||||
// NotificationsGet
|
// 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
|
||||||
NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode
|
NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode
|
||||||
|
|
||||||
|
@ -192,11 +192,11 @@ type Processor interface {
|
||||||
StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode)
|
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 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 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 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 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)
|
AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, gtserror.WithCode)
|
||||||
|
|
|
@ -137,40 +137,47 @@ func StatusSkipInsertFunction() timeline.SkipInsertFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.TimelineResponse, gtserror.WithCode) {
|
func (p *processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||||
preparedItems, err := p.statusTimelines.GetTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
|
preparedItems, err := p.statusTimelines.GetTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(preparedItems) == 0 {
|
count := len(preparedItems)
|
||||||
return util.EmptyTimelineResponse(), nil
|
|
||||||
|
if count == 0 {
|
||||||
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineables := []timeline.Timelineable{}
|
items := []interface{}{}
|
||||||
for _, i := range preparedItems {
|
nextMaxIDValue := ""
|
||||||
status, ok := i.(*apimodel.Status)
|
prevMinIDValue := ""
|
||||||
if !ok {
|
for i, item := range preparedItems {
|
||||||
return nil, gtserror.NewErrorInternalError(errors.New("error converting prepared timeline entry to api status"))
|
if i == count-1 {
|
||||||
|
nextMaxIDValue = item.GetID()
|
||||||
}
|
}
|
||||||
timelineables = append(timelineables, status)
|
|
||||||
|
if i == 0 {
|
||||||
|
prevMinIDValue = item.GetID()
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.PackageTimelineableResponse(util.TimelineableResponseParams{
|
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||||
Items: timelineables,
|
Items: items,
|
||||||
Path: "api/v1/timelines/home",
|
Path: "api/v1/timelines/home",
|
||||||
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
|
NextMaxIDValue: nextMaxIDValue,
|
||||||
PrevMinIDValue: timelineables[0].GetID(),
|
PrevMinIDValue: prevMinIDValue,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.TimelineResponse, gtserror.WithCode) {
|
func (p *processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||||
statuses, err := p.db.GetPublicTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
|
statuses, err := p.db.GetPublicTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
// there are just no entries left
|
// there are just no entries left
|
||||||
return util.EmptyTimelineResponse(), nil
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
// there's an actual error
|
// there's an actual error
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
@ -181,30 +188,41 @@ func (p *processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, m
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(filtered) == 0 {
|
count := len(filtered)
|
||||||
return util.EmptyTimelineResponse(), nil
|
|
||||||
|
if count == 0 {
|
||||||
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineables := []timeline.Timelineable{}
|
items := []interface{}{}
|
||||||
for _, i := range filtered {
|
nextMaxIDValue := ""
|
||||||
timelineables = append(timelineables, i)
|
prevMinIDValue := ""
|
||||||
|
for i, item := range filtered {
|
||||||
|
if i == count-1 {
|
||||||
|
nextMaxIDValue = item.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
prevMinIDValue = item.GetID()
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.PackageTimelineableResponse(util.TimelineableResponseParams{
|
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||||
Items: timelineables,
|
Items: items,
|
||||||
Path: "api/v1/timelines/public",
|
Path: "api/v1/timelines/public",
|
||||||
NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
|
NextMaxIDValue: nextMaxIDValue,
|
||||||
PrevMinIDValue: timelineables[0].GetID(),
|
PrevMinIDValue: prevMinIDValue,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.TimelineResponse, gtserror.WithCode) {
|
func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||||
statuses, nextMaxID, prevMinID, err := p.db.GetFavedTimeline(ctx, authed.Account.ID, maxID, minID, limit)
|
statuses, nextMaxID, prevMinID, err := p.db.GetFavedTimeline(ctx, authed.Account.ID, maxID, minID, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
// there are just no entries left
|
// there are just no entries left
|
||||||
return util.EmptyTimelineResponse(), nil
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
// there's an actual error
|
// there's an actual error
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
@ -216,16 +234,16 @@ func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, ma
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(filtered) == 0 {
|
if len(filtered) == 0 {
|
||||||
return util.EmptyTimelineResponse(), nil
|
return util.EmptyPageableResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineables := []timeline.Timelineable{}
|
items := []interface{}{}
|
||||||
for _, i := range filtered {
|
for _, item := range filtered {
|
||||||
timelineables = append(timelineables, i)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.PackageTimelineableResponse(util.TimelineableResponseParams{
|
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||||
Items: timelineables,
|
Items: items,
|
||||||
Path: "api/v1/favourites",
|
Path: "api/v1/favourites",
|
||||||
NextMaxIDValue: nextMaxID,
|
NextMaxIDValue: nextMaxID,
|
||||||
PrevMinIDValue: prevMinID,
|
PrevMinIDValue: prevMinID,
|
||||||
|
|
119
internal/util/paging.go
Normal file
119
internal/util/paging.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PageableResponseParams models the parameters to pass to PackagePageableResponse.
|
||||||
|
//
|
||||||
|
// The given items will be provided in the paged response.
|
||||||
|
//
|
||||||
|
// The other values are all used to create the Link header so that callers know
|
||||||
|
// which endpoint to query next and previously in order to do paging.
|
||||||
|
type PageableResponseParams struct {
|
||||||
|
Items []interface{} // Sorted slice of items (statuses, notifications, etc)
|
||||||
|
Path string // path to use for next/prev queries in the link header
|
||||||
|
NextMaxIDKey string // key to use for the next max id query param in the link header, defaults to 'max_id'
|
||||||
|
NextMaxIDValue string // value to use for next max id
|
||||||
|
PrevMinIDKey string // key to use for the prev min id query param in the link header, defaults to 'min_id'
|
||||||
|
PrevMinIDValue string // value to use for prev min id
|
||||||
|
Limit int // limit number of entries to return
|
||||||
|
ExtraQueryParams []string // any extra query parameters to provide in the link header, should be in the format 'example=value'
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackagePageableResponse is a convenience function for returning
|
||||||
|
// a bunch of pageable items (notifications, statuses, etc), as well
|
||||||
|
// as a Link header to inform callers of where to find next/prev items.
|
||||||
|
func PackagePageableResponse(params PageableResponseParams) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||||
|
if params.NextMaxIDKey == "" {
|
||||||
|
params.NextMaxIDKey = "max_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.PrevMinIDKey == "" {
|
||||||
|
params.PrevMinIDKey = "min_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
pageableResponse := EmptyPageableResponse()
|
||||||
|
|
||||||
|
if len(params.Items) == 0 {
|
||||||
|
return pageableResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// items
|
||||||
|
pageableResponse.Items = params.Items
|
||||||
|
|
||||||
|
protocol := config.GetProtocol()
|
||||||
|
host := config.GetHost()
|
||||||
|
|
||||||
|
// next
|
||||||
|
nextRaw := params.NextMaxIDKey + "=" + params.NextMaxIDValue
|
||||||
|
if params.Limit != 0 {
|
||||||
|
nextRaw = fmt.Sprintf("limit=%d&", params.Limit) + nextRaw
|
||||||
|
}
|
||||||
|
for _, p := range params.ExtraQueryParams {
|
||||||
|
nextRaw = nextRaw + "&" + p
|
||||||
|
}
|
||||||
|
nextLink := &url.URL{
|
||||||
|
Scheme: protocol,
|
||||||
|
Host: host,
|
||||||
|
Path: params.Path,
|
||||||
|
RawQuery: nextRaw,
|
||||||
|
}
|
||||||
|
nextLinkString := nextLink.String()
|
||||||
|
pageableResponse.NextLink = nextLinkString
|
||||||
|
|
||||||
|
// prev
|
||||||
|
prevRaw := params.PrevMinIDKey + "=" + params.PrevMinIDValue
|
||||||
|
if params.Limit != 0 {
|
||||||
|
prevRaw = fmt.Sprintf("limit=%d&", params.Limit) + prevRaw
|
||||||
|
}
|
||||||
|
for _, p := range params.ExtraQueryParams {
|
||||||
|
prevRaw = prevRaw + "&" + p
|
||||||
|
}
|
||||||
|
prevLink := &url.URL{
|
||||||
|
Scheme: protocol,
|
||||||
|
Host: host,
|
||||||
|
Path: params.Path,
|
||||||
|
RawQuery: prevRaw,
|
||||||
|
}
|
||||||
|
prevLinkString := prevLink.String()
|
||||||
|
pageableResponse.PrevLink = prevLinkString
|
||||||
|
|
||||||
|
// link header
|
||||||
|
next := fmt.Sprintf("<%s>; rel=\"next\"", nextLinkString)
|
||||||
|
prev := fmt.Sprintf("<%s>; rel=\"prev\"", prevLinkString)
|
||||||
|
pageableResponse.LinkHeader = next + ", " + prev
|
||||||
|
|
||||||
|
return pageableResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyPageableResponse just returns an empty
|
||||||
|
// PageableResponse with no link header or items.
|
||||||
|
func EmptyPageableResponse() *apimodel.PageableResponse {
|
||||||
|
return &apimodel.PageableResponse{
|
||||||
|
Items: []interface{}{},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
GoToSocial
|
|
||||||
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TimelineableResponseParams models the parameters to pass to PackageTimelineableResponse.
|
|
||||||
//
|
|
||||||
// The given items will be provided in the timeline response.
|
|
||||||
//
|
|
||||||
// The other values are all used to create the Link header so that callers know
|
|
||||||
// which endpoint to query next and previously in order to do paging.
|
|
||||||
type TimelineableResponseParams struct {
|
|
||||||
Items []timeline.Timelineable // Sorted slice of Timelineables (statuses, notifications, etc)
|
|
||||||
Path string // path to use for next/prev queries in the link header
|
|
||||||
NextMaxIDKey string // key to use for the next max id query param in the link header, defaults to 'max_id'
|
|
||||||
NextMaxIDValue string // value to use for next max id
|
|
||||||
PrevMinIDKey string // key to use for the prev min id query param in the link header, defaults to 'min_id'
|
|
||||||
PrevMinIDValue string // value to use for prev min id
|
|
||||||
Limit int // limit number of entries to return
|
|
||||||
ExtraQueryParams []string // any extra query parameters to provide in the link header, should be in the format 'example=value'
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackageTimelineableResponse is a convenience function for returning
|
|
||||||
// a bunch of timelineable items (notifications, statuses, etc), as well
|
|
||||||
// as a Link header to inform callers of where to find next/prev items.
|
|
||||||
func PackageTimelineableResponse(params TimelineableResponseParams) (*apimodel.TimelineResponse, gtserror.WithCode) {
|
|
||||||
if params.NextMaxIDKey == "" {
|
|
||||||
params.NextMaxIDKey = "max_id"
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.PrevMinIDKey == "" {
|
|
||||||
params.PrevMinIDKey = "min_id"
|
|
||||||
}
|
|
||||||
|
|
||||||
timelineResponse := &apimodel.TimelineResponse{
|
|
||||||
Items: params.Items,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(params.Items) != 0 {
|
|
||||||
protocol := config.GetProtocol()
|
|
||||||
host := config.GetHost()
|
|
||||||
|
|
||||||
// next
|
|
||||||
nextRaw := params.NextMaxIDKey + "=" + params.NextMaxIDValue
|
|
||||||
if params.Limit != 0 {
|
|
||||||
nextRaw = fmt.Sprintf("limit=%d&", params.Limit) + nextRaw
|
|
||||||
}
|
|
||||||
for _, p := range params.ExtraQueryParams {
|
|
||||||
nextRaw = nextRaw + "&" + p
|
|
||||||
}
|
|
||||||
nextLink := &url.URL{
|
|
||||||
Scheme: protocol,
|
|
||||||
Host: host,
|
|
||||||
Path: params.Path,
|
|
||||||
RawQuery: nextRaw,
|
|
||||||
}
|
|
||||||
nextLinkString := nextLink.String()
|
|
||||||
timelineResponse.NextLink = nextLinkString
|
|
||||||
|
|
||||||
// prev
|
|
||||||
prevRaw := params.PrevMinIDKey + "=" + params.PrevMinIDValue
|
|
||||||
if params.Limit != 0 {
|
|
||||||
prevRaw = fmt.Sprintf("limit=%d&", params.Limit) + prevRaw
|
|
||||||
}
|
|
||||||
for _, p := range params.ExtraQueryParams {
|
|
||||||
prevRaw = prevRaw + "&" + p
|
|
||||||
}
|
|
||||||
prevLink := &url.URL{
|
|
||||||
Scheme: protocol,
|
|
||||||
Host: host,
|
|
||||||
Path: params.Path,
|
|
||||||
RawQuery: prevRaw,
|
|
||||||
}
|
|
||||||
prevLinkString := prevLink.String()
|
|
||||||
timelineResponse.PrevLink = prevLinkString
|
|
||||||
|
|
||||||
// link header
|
|
||||||
next := fmt.Sprintf("<%s>; rel=\"next\"", nextLinkString)
|
|
||||||
prev := fmt.Sprintf("<%s>; rel=\"prev\"", prevLinkString)
|
|
||||||
timelineResponse.LinkHeader = next + ", " + prev
|
|
||||||
}
|
|
||||||
|
|
||||||
return timelineResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmptyTimelineResponse just returns an empty
|
|
||||||
// TimelineResponse with no link header or items.
|
|
||||||
func EmptyTimelineResponse() *apimodel.TimelineResponse {
|
|
||||||
return &apimodel.TimelineResponse{
|
|
||||||
Items: []timeline.Timelineable{},
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue