package httpapi import ( "net/http" "strconv" "strings" ) const ( defaultPageLimit = 50 maxPageLimit = 200 maxTreePageLimit = 2000 ) // QuerySearch returns the list/search text from query params. // Accepts both `q` (canonical) and `search` (UI/legacy alias). func QuerySearch(r *http.Request) string { q := strings.TrimSpace(r.URL.Query().Get("q")) if q != "" { return q } return strings.TrimSpace(r.URL.Query().Get("search")) } // QueryTruthy reports whether a query param is an explicit truthy flag // (1/true/yes/on). Empty or unrecognized values are false. func QueryTruthy(r *http.Request, key string) bool { v := strings.ToLower(strings.TrimSpace(r.URL.Query().Get(key))) switch v { case "1", "true", "yes", "on": return true default: return false } } // QueryDetailed is true when the client opts into full product list fields // (JSONB attributes, descriptions, quality scoring inputs) via detailed=1. func QueryDetailed(r *http.Request) bool { return QueryTruthy(r, "detailed") } // ParseLimitOffset reads limit/offset query params with safe defaults and caps. // Oversized limits are clamped to maxPageLimit. func ParseLimitOffset(r *http.Request) (limit, offset int) { return ParseLimitOffsetMax(r, maxPageLimit) } // ParseLimitOffsetMax allows a higher per-endpoint cap and clamps to max // (used for category tree loads). func ParseLimitOffsetMax(r *http.Request, max int) (limit, offset int) { if max <= 0 { max = maxPageLimit } limit, _ = strconv.Atoi(r.URL.Query().Get("limit")) offset, _ = strconv.Atoi(r.URL.Query().Get("offset")) if limit <= 0 { limit = defaultPageLimit } if limit > max { limit = max } if offset < 0 { offset = 0 } return limit, offset } // ParsePageLimitOffset supports legacy page/limit and v2 limit/offset. // When page is set, offset = (page-1)*limit with legacy defaults (limit=25, max 100). // When only offset/limit are set (no page), uses ParseLimitOffset defaults (limit=50, max 200). func ParsePageLimitOffset(r *http.Request) (page, limit, offset int) { pageRaw := strings.TrimSpace(r.URL.Query().Get("page")) if pageRaw == "" { limit, offset = ParseLimitOffset(r) page = 1 if limit > 0 { page = offset/limit + 1 } return page, limit, offset } page, _ = strconv.Atoi(pageRaw) if page < 1 { page = 1 } limit, _ = strconv.Atoi(r.URL.Query().Get("limit")) if limit <= 0 { limit = 25 } if limit > 100 { limit = 100 } offset = (page - 1) * limit return page, limit, offset } // pageSlice returns a bounded page of items and the original total length. func pageSlice[T any](items []T, limit, offset int) (page []T, total int) { total = len(items) if offset >= total { return []T{}, total } end := offset + limit if end > total { end = total } return items[offset:end], total }