dbots/internal/paginate/paginate.go
2026-04-17 22:05:05 +02:00

87 lines
1.8 KiB
Go

package paginate
import (
"net/http"
"strconv"
)
const (
DefaultLimit = 20
MaxLimit = 100
)
// PageInfo holds pagination metadata returned alongside the data.
type PageInfo struct {
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
HasNext bool `json:"has_next"`
HasPrev bool `json:"has_prev"`
}
// Page is the generic paginated response envelope.
type Page[T any] struct {
Data []T `json:"data"`
Pagination PageInfo `json:"pagination"`
}
// NewPage constructs a Page from a slice already fetched from the DB
// (which owns the limit/offset), the total record count, and the Params
// that were passed to the query.
func NewPage[T any](data []T, total int, p Params) Page[T] {
if data == nil {
data = []T{}
}
return Page[T]{
Data: data,
Pagination: PageInfo{
Total: total,
Limit: int(p.Limit),
Offset: int(p.Offset),
HasNext: int(p.Offset)+int(p.Limit) < total,
HasPrev: p.Offset > 0,
},
}
}
// Params holds parsed limit/offset values
type Params struct {
Limit int32
Offset int32
}
// ParseParams reads "limit" and "offset" from the request query string and
// returns safe, clamped values. Invalid or missing values fall back to defaults.
//
// GET /bots?limit=10&offset=30
func ParseParams(r *http.Request) Params {
limit := parseIntParam(r, "limit", DefaultLimit)
offset := parseIntParam(r, "offset", 0)
if limit <= 0 {
limit = DefaultLimit
}
if limit > MaxLimit {
limit = MaxLimit
}
if offset < 0 {
offset = 0
}
return Params{
Limit: int32(limit),
Offset: int32(offset),
}
}
func parseIntParam(r *http.Request, key string, fallback int) int {
raw := r.URL.Query().Get(key)
if raw == "" {
return fallback
}
v, err := strconv.Atoi(raw)
if err != nil {
return fallback
}
return v
}