commit 832befd727a257581d774bb6bbdeb0f81cd658e2
parent 80663061d8f361ae4bcea1307a10a40c41174ebe
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Mon, 10 Oct 2022 15:52:49 +0200
[chore] Make paging logic more generic (#901)
* make paging logic more generic
not just for timelines!
* linty linterson
Diffstat:
10 files changed, 281 insertions(+), 224 deletions(-)
diff --git a/internal/api/model/paging.go b/internal/api/model/paging.go
@@ -0,0 +1,28 @@
+/*
+ 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 model
+
+// 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
+}
diff --git a/internal/api/model/timeline.go b/internal/api/model/timeline.go
@@ -1,30 +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 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
- LinkHeader string
- NextLink string
- PrevLink string
-}
diff --git a/internal/processing/account.go b/internal/processing/account.go
@@ -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)
}
diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go
@@ -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.
diff --git a/internal/processing/account/getstatuses.go b/internal/processing/account/getstatuses.go
@@ -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)
@@ -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)
}
@@ -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),
@@ -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 {
@@ -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{},
})
}
diff --git a/internal/processing/notification.go b/internal/processing/notification.go
@@ -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,
})
}
diff --git a/internal/processing/processor.go b/internal/processing/processor.go
@@ -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.
@@ -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
@@ -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)
diff --git a/internal/processing/statustimeline.go b/internal/processing/statustimeline.go
@@ -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)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
- if len(preparedItems) == 0 {
- return util.EmptyTimelineResponse(), nil
+ count := len(preparedItems)
+
+ if count == 0 {
+ return util.EmptyPageableResponse(), nil
}
- timelineables := []timeline.Timelineable{}
- for _, i := range preparedItems {
- status, ok := i.(*apimodel.Status)
- if !ok {
- return nil, gtserror.NewErrorInternalError(errors.New("error converting prepared timeline entry to api status"))
+ items := []interface{}{}
+ nextMaxIDValue := ""
+ prevMinIDValue := ""
+ for i, item := range preparedItems {
+ 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{
- Items: timelineables,
+ return util.PackagePageableResponse(util.PageableResponseParams{
+ Items: items,
Path: "api/v1/timelines/home",
- NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
- PrevMinIDValue: timelineables[0].GetID(),
+ NextMaxIDValue: nextMaxIDValue,
+ PrevMinIDValue: prevMinIDValue,
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)
if err != nil {
if err == db.ErrNoEntries {
// there are just no entries left
- return util.EmptyTimelineResponse(), nil
+ return util.EmptyPageableResponse(), nil
}
// there's an actual error
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)
}
- 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 {
- timelineables = append(timelineables, i)
+ items := []interface{}{}
+ nextMaxIDValue := ""
+ 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{
- Items: timelineables,
+ return util.PackagePageableResponse(util.PageableResponseParams{
+ Items: items,
Path: "api/v1/timelines/public",
- NextMaxIDValue: timelineables[len(timelineables)-1].GetID(),
- PrevMinIDValue: timelineables[0].GetID(),
+ NextMaxIDValue: nextMaxIDValue,
+ PrevMinIDValue: prevMinIDValue,
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)
if err != nil {
if err == db.ErrNoEntries {
// there are just no entries left
- return util.EmptyTimelineResponse(), nil
+ return util.EmptyPageableResponse(), nil
}
// there's an actual error
return nil, gtserror.NewErrorInternalError(err)
@@ -216,16 +234,16 @@ func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, ma
}
if len(filtered) == 0 {
- return util.EmptyTimelineResponse(), nil
+ return util.EmptyPageableResponse(), nil
}
- timelineables := []timeline.Timelineable{}
- for _, i := range filtered {
- timelineables = append(timelineables, i)
+ items := []interface{}{}
+ for _, item := range filtered {
+ items = append(items, item)
}
- return util.PackageTimelineableResponse(util.TimelineableResponseParams{
- Items: timelineables,
+ return util.PackagePageableResponse(util.PageableResponseParams{
+ Items: items,
Path: "api/v1/favourites",
NextMaxIDValue: nextMaxID,
PrevMinIDValue: prevMinID,
diff --git a/internal/util/paging.go b/internal/util/paging.go
@@ -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{}{},
+ }
+}
diff --git a/internal/util/timeline.go b/internal/util/timeline.go
@@ -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{},
- }
-}