77 lines
2.9 KiB
Go
77 lines
2.9 KiB
Go
package data
|
|
|
|
import (
|
|
"greenlight.craftr.fr/internal/validator"
|
|
"strings"
|
|
)
|
|
|
|
type Filters struct {
|
|
Page int
|
|
PageSize int
|
|
Sort string
|
|
SortSafelist []string
|
|
}
|
|
|
|
func ValidateFilters(v *validator.Validator, f Filters) {
|
|
// Check that the page and page_size parameters contain sensible values
|
|
v.Check(f.Page > 0, "page", "must be greater than 0")
|
|
v.Check(f.Page <= 10_000_000, "page", "must be a maximum of 10 million")
|
|
v.Check(f.PageSize > 0, "page_size", "must be greater than 0")
|
|
v.Check(f.PageSize <= 100, "page_size", "must be a maximum of 100")
|
|
|
|
// Check that the sort parameter matches a value in the safelist
|
|
v.Check(validator.PermittedValue(f.Sort, f.SortSafelist...), "sort", "invalid sort value")
|
|
}
|
|
|
|
func (f Filters) limit() int {
|
|
return f.PageSize
|
|
}
|
|
|
|
func (f Filters) offset() int {
|
|
return (f.Page - 1) * f.PageSize
|
|
}
|
|
|
|
// sortColumn : Check that the client-provided Sort field matches one of the entries in our safelist and if it does, extract the column name from the Sort field by stripping the leading hyphen character (if one exists)
|
|
func (f Filters) sortColumn() string {
|
|
for _, safeValue := range f.SortSafelist {
|
|
if f.Sort == safeValue {
|
|
return strings.TrimPrefix(f.Sort, "-")
|
|
}
|
|
}
|
|
|
|
// It will panic if the client-provided 'Sort' value doesn't match one of the entries in our safelist. In theory, this shouldn't happen - the 'Sort' value should have already been checked by calling the 'ValidateFilters()' function - but this is a sensible failsafe to help stop a SQL injection attack occurring
|
|
panic("unsafe sort parameter: " + f.Sort)
|
|
}
|
|
|
|
// sortDirection : Return the sort direction ("ASC" or "DESC") depending on the prefix character of the Sort field
|
|
func (f Filters) sortDirection() string {
|
|
if strings.HasPrefix(f.Sort, "-") {
|
|
return "DESC"
|
|
}
|
|
return "ASC"
|
|
}
|
|
|
|
// Metadata struct for holding the pagination metadata
|
|
type Metadata struct {
|
|
CurrentPage int `json:"current_page,omitempty"`
|
|
PageSize int `json:"page_size,omitempty"`
|
|
FirstPage int `json:"first_page,omitempty"`
|
|
LastPage int `json:"last_page,omitempty"`
|
|
TotalRecords int `json:"total_records,omitempty"`
|
|
}
|
|
|
|
// calculateMetadata : calculates the appropriate pagination metadata values given the total number of records, current page, and page size values. Note that when the last page value is calculated, we are dividing two int values, and when dividing integer types in Go the result will also be an integer type, with the modulus (or reminder) dropped. So, for instance, if there were 12 records in total and a page of 5, the last page value would be (12+5-1)/5 = 3.2, which is then truncated to 3 by Go.
|
|
func calculateMetadata(totalRecords, page, pageSize int) Metadata {
|
|
if totalRecords == 0 {
|
|
return Metadata{}
|
|
}
|
|
|
|
return Metadata{
|
|
CurrentPage: page,
|
|
PageSize: pageSize,
|
|
FirstPage: 1,
|
|
LastPage: (totalRecords + pageSize - 1) / pageSize,
|
|
TotalRecords: totalRecords,
|
|
}
|
|
}
|