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 }