summaryrefslogtreecommitdiff
path: root/internal/util/paging.go
blob: 190f40afd587ecae24574be78b3e95fdabb096be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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"
	"strings"

	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) {
	// Set default paging values, if
	// they weren't set by the caller.
	if params.NextMaxIDKey == "" {
		params.NextMaxIDKey = "max_id"
	}

	if params.PrevMinIDKey == "" {
		params.PrevMinIDKey = "min_id"
	}

	var (
		protocol        = config.GetProtocol()
		host            = config.GetHost()
		nextLink        string
		prevLink        string
		linkHeaderParts = make([]string, 0, 2)
	)

	// Parse next link.
	if params.NextMaxIDValue != "" {
		nextRaw := params.NextMaxIDKey + "=" + params.NextMaxIDValue

		if params.Limit != 0 {
			nextRaw = fmt.Sprintf("limit=%d&", params.Limit) + nextRaw
		}

		for _, p := range params.ExtraQueryParams {
			nextRaw += "&" + p
		}

		nextLink = func() string {
			u := &url.URL{
				Scheme:   protocol,
				Host:     host,
				Path:     params.Path,
				RawQuery: nextRaw,
			}
			return u.String()
		}()

		linkHeaderParts = append(linkHeaderParts, `<`+nextLink+`>; rel="next"`)
	}

	// Parse prev link.
	if params.PrevMinIDValue != "" {
		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 = func() string {
			u := &url.URL{
				Scheme:   protocol,
				Host:     host,
				Path:     params.Path,
				RawQuery: prevRaw,
			}
			return u.String()
		}()

		linkHeaderParts = append(linkHeaderParts, `<`+prevLink+`>; rel="prev"`)
	}

	return &apimodel.PageableResponse{
		Items:      params.Items,
		LinkHeader: strings.Join(linkHeaderParts, ", "),
		NextLink:   nextLink,
		PrevLink:   prevLink,
	}, nil
}

// EmptyPageableResponse just returns an empty
// PageableResponse with no link header or items.
func EmptyPageableResponse() *apimodel.PageableResponse {
	return &apimodel.PageableResponse{
		Items: []interface{}{},
	}
}