Improve pagination (#105)
This commit is contained in:
36
vendor/code.vikunja.io/web/Readme.md
generated
vendored
36
vendor/code.vikunja.io/web/Readme.md
generated
vendored
@ -60,7 +60,7 @@ This interface defines methods to Create/Read/ReadAll/Update/Delete something. I
|
||||
type CRUDable interface {
|
||||
Create(Auth) error
|
||||
ReadOne() error
|
||||
ReadAll(string, Auth, int) (interface{}, error)
|
||||
ReadAll(auth Auth, search string, page int64, perPage int64) (result interface{}, resultCount int64, numberOfPages int64, err error)
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
@ -122,6 +122,13 @@ You can provide your own instance of `logger.Logger` (using [go-logging](https:/
|
||||
It will use this instance to log errors which are not better specified or things like users trying to do something they're
|
||||
not allowed to do and so on.
|
||||
|
||||
#### MaxItemsPerPage
|
||||
|
||||
Contains the maximum number of items per page.
|
||||
If the client requests more items than this, the number of items requested is set to this value.
|
||||
|
||||
See [pagination](#pagination) for more.
|
||||
|
||||
#### Full Example
|
||||
|
||||
```go
|
||||
@ -137,18 +144,35 @@ handler.SetLoggingProvider(&log.Log)
|
||||
|
||||
### Pagination
|
||||
|
||||
When using the `ReadAll`-method, the third parameter contains the requested page.
|
||||
Your function should return only the number of results corresponding to that page.
|
||||
The number of items per page should be set by your application elewhere, most likely in its config.
|
||||
The `ReadAll`-method has a number of parameters:
|
||||
|
||||
The number of items to return is then usually calculated with some method like `page_number * items_per_page`.
|
||||
```go
|
||||
ReadAll(auth Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfItems int64, err error)
|
||||
```
|
||||
|
||||
The third parameter contains the requested page, the fourth parameter contains the number of items per page.
|
||||
You should calculate the limits accordingly.
|
||||
|
||||
If the number of items per page are not set by the client, the web handler will pass the maximum number of items per page instead.
|
||||
This makes items per page optional for clients.
|
||||
Take a look at [the config section](#handler-config) for information on how to set that value.
|
||||
|
||||
You need to return a number of things:
|
||||
|
||||
* The result itself, usually a slice
|
||||
* The number of items you return in `result`. Most of the time, this is just `len(result)`. You need to return this value to make the clients aware if they requested a number of items > max items per page.
|
||||
* The total number of items available. We use the total number of items here and not the number pages so the implementations don't have to deal with calculating the number of pages from that. The total number of clients is then calculated and returned to the client, ite can then be used by the clients to build client-side pagination or similar.
|
||||
* An error.
|
||||
|
||||
The number of items and the total number of pages available will be returned in the `x-pagination-total-pages` and `x-pagination-result-count` response headers.
|
||||
_You should put this in your api documentation._
|
||||
|
||||
### Search
|
||||
|
||||
When using the `ReadAll`-method, the first parameter is a search term which should be used to search items of your struct.
|
||||
You define the critera inside of that function.
|
||||
|
||||
Users can then pass the `?s=something` parameter to the url to search, thats something you should put in your api documentation.
|
||||
Users can then pass the `?s=something` parameter to the url to search, _thats something you should put in your api documentation_.
|
||||
|
||||
As the logic for "give me everything" and "give me everything where the name contains 'something'" is mostly the same, we made
|
||||
the decision to design the function like this, in order to keep the places with mostly the same logic as few as possible.
|
||||
|
9
vendor/code.vikunja.io/web/handler/config.go
generated
vendored
9
vendor/code.vikunja.io/web/handler/config.go
generated
vendored
@ -21,9 +21,11 @@ import (
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
// Config contains the config for the web handler
|
||||
type Config struct {
|
||||
AuthProvider *web.Auths
|
||||
LoggingProvider *logging.Logger
|
||||
MaxItemsPerPage int
|
||||
}
|
||||
|
||||
var config *Config
|
||||
@ -32,10 +34,17 @@ func init() {
|
||||
config = &Config{}
|
||||
}
|
||||
|
||||
// SetAuthProvider sets the auth provider in config
|
||||
func SetAuthProvider(provider *web.Auths) {
|
||||
config.AuthProvider = provider
|
||||
}
|
||||
|
||||
// SetLoggingProvider sets the logging provider in the config
|
||||
func SetLoggingProvider(logger *logging.Logger) {
|
||||
config.LoggingProvider = logger
|
||||
}
|
||||
|
||||
// SetMaxItemsPerPage sets the max number of items per page in the config
|
||||
func SetMaxItemsPerPage(maxItemsPerPage int) {
|
||||
config.MaxItemsPerPage = maxItemsPerPage
|
||||
}
|
||||
|
47
vendor/code.vikunja.io/web/handler/read_all.go
generated
vendored
47
vendor/code.vikunja.io/web/handler/read_all.go
generated
vendored
@ -17,6 +17,7 @@ package handler
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
@ -47,16 +48,56 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
|
||||
}
|
||||
if pageNumber < 0 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Page number cannot be negative.")
|
||||
}
|
||||
|
||||
// Items per page
|
||||
var perPageNumber int
|
||||
perPage := ctx.QueryParam("per_page")
|
||||
// If we dont have an "items per page" parameter, we want to use the default.
|
||||
// To prevent Atoi from failing, we check this here.
|
||||
if perPage != "" {
|
||||
perPageNumber, err = strconv.Atoi(perPage)
|
||||
if err != nil {
|
||||
config.LoggingProvider.Error(err.Error())
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad per page amount requested.")
|
||||
}
|
||||
}
|
||||
// Set default page count
|
||||
if perPageNumber == 0 {
|
||||
perPageNumber = config.MaxItemsPerPage
|
||||
}
|
||||
if perPageNumber < 1 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Per page amount cannot be negative.")
|
||||
}
|
||||
if perPageNumber > config.MaxItemsPerPage {
|
||||
perPageNumber = config.MaxItemsPerPage
|
||||
}
|
||||
|
||||
// Search
|
||||
search := ctx.QueryParam("s")
|
||||
|
||||
lists, err := currentStruct.ReadAll(search, currentAuth, pageNumber)
|
||||
result, resultCount, numberOfItems, err := currentStruct.ReadAll(currentAuth, search, pageNumber, perPageNumber)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err, ctx)
|
||||
}
|
||||
|
||||
return ctx.JSON(http.StatusOK, lists)
|
||||
// Calculate the number of pages from the number of items
|
||||
// We always round up, because if we don't have a number of items which is exactly dividable by the number of items per page,
|
||||
// we would get a result that is one page off.
|
||||
var numberOfPages = math.Ceil(float64(numberOfItems) / float64(perPageNumber))
|
||||
// If we return all results, we only have one page
|
||||
if pageNumber < 0 {
|
||||
numberOfPages = 1
|
||||
}
|
||||
// If we don't have results, we don't have a page
|
||||
if resultCount == 0 {
|
||||
numberOfPages = 0
|
||||
}
|
||||
|
||||
ctx.Response().Header().Set("x-pagination-total-pages", strconv.FormatFloat(numberOfPages, 'f', 0, 64))
|
||||
ctx.Response().Header().Set("x-pagination-result-count", strconv.FormatInt(int64(resultCount), 10))
|
||||
ctx.Response().Header().Set("Access-Control-Expose-Headers", "x-pagination-total-pages, x-pagination-result-count")
|
||||
|
||||
return ctx.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
2
vendor/code.vikunja.io/web/web.go
generated
vendored
2
vendor/code.vikunja.io/web/web.go
generated
vendored
@ -31,7 +31,7 @@ type Rights interface {
|
||||
type CRUDable interface {
|
||||
Create(Auth) error
|
||||
ReadOne() error
|
||||
ReadAll(string, Auth, int) (interface{}, error)
|
||||
ReadAll(auth Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error)
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
|
Reference in New Issue
Block a user