package db import ( "net/http" "strings" "git.haelnorr.com/h/golib/hws" "git.haelnorr.com/h/oslstats/internal/validation" "github.com/uptrace/bun" ) type PageOpts struct { Page int PerPage int Order bun.Order OrderBy string } type OrderOpts struct { Order bun.Order OrderBy string Label string } func GetPageOpts(s *hws.Server, w http.ResponseWriter, r *http.Request) (*PageOpts, bool) { var getter validation.Getter switch r.Method { case "GET": getter = validation.NewQueryGetter(r) case "POST": var ok bool getter, ok = validation.ParseFormOrError(s, w, r) if !ok { return nil, false } default: return nil, false } return getPageOpts(s, w, r, getter), true } func getPageOpts(s *hws.Server, w http.ResponseWriter, r *http.Request, g validation.Getter) *PageOpts { page := g.Int("page").Optional().Min(1).Value perPage := g.Int("per_page").Optional().Min(1).Max(100).Value order := g.String("order").TrimSpace().ToUpper().Optional().AllowedValues([]string{"ASC", "DESC"}).Value orderBy := g.String("order_by").TrimSpace().Optional().ToLower().Value valid := g.ValidateAndError(s, w, r) if !valid { return nil } pageOpts := &PageOpts{ Page: page, PerPage: perPage, Order: bun.Order(order), OrderBy: orderBy, } return pageOpts } func setPageOpts(q *bun.SelectQuery, p, d *PageOpts, totalitems int) (*bun.SelectQuery, *PageOpts) { if p == nil { p = new(PageOpts) } if p.Page <= 0 { p.Page = d.Page } if p.PerPage == 0 { p.PerPage = d.PerPage } maxpage := p.TotalPages(totalitems) if p.Page > maxpage && maxpage > 0 { p.Page = maxpage } if p.Order == "" { p.Order = d.Order } if p.OrderBy == "" { p.OrderBy = d.OrderBy } p.OrderBy = sanitiseOrderBy(p.OrderBy) q = q.OrderBy(p.OrderBy, p.Order). Limit(p.PerPage). Offset(p.PerPage * (p.Page - 1)) return q, p } func sanitiseOrderBy(orderby string) string { result := strings.ToLower(orderby) var builder strings.Builder for _, r := range result { if isValidChar(r) { builder.WriteRune(r) } } sanitized := builder.String() if sanitized == "" { return "_" } if !isValidFirstChar(rune(sanitized[0])) { sanitized = "_" + sanitized } if len(sanitized) > 63 { sanitized = sanitized[:63] } return sanitized } func isValidChar(r rune) bool { return (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' } func isValidFirstChar(r rune) bool { return (r >= 'a' && r <= 'z') || r == '_' } // TotalPages calculates the total number of pages func (p *PageOpts) TotalPages(total int) int { if p.PerPage == 0 { return 0 } pages := total / p.PerPage if total%p.PerPage > 0 { pages++ } return pages } // HasPrevPage checks if there is a previous page func (p *PageOpts) HasPrevPage() bool { return p.Page > 1 } // HasNextPage checks if there is a next page func (p *PageOpts) HasNextPage(total int) bool { return p.Page < p.TotalPages(total) } // GetPageRange returns an array of page numbers to display // maxButtons controls how many page buttons to show func (p *PageOpts) GetPageRange(total int, maxButtons int) []int { totalPages := p.TotalPages(total) if totalPages == 0 { return []int{} } // If total pages is less than max buttons, show all pages if totalPages <= maxButtons { pages := make([]int, totalPages) for i := range totalPages { pages[i] = i + 1 } return pages } // Calculate range around current page halfButtons := maxButtons / 2 start := p.Page - halfButtons end := p.Page + halfButtons // Adjust if at beginning if start < 1 { start = 1 end = maxButtons } // Adjust if at end if end > totalPages { end = totalPages start = totalPages - maxButtons + 1 } pages := make([]int, 0, maxButtons) for i := start; i <= end; i++ { pages = append(pages, i) } return pages } // StartItem returns the number of the first item on the current page func (p *PageOpts) StartItem() int { if p.Page < 1 { return 0 } return (p.Page-1)*p.PerPage + 1 } // EndItem returns the number of the last item on the current page func (p *PageOpts) EndItem(total int) int { end := p.Page * p.PerPage if end > total { return total } return end }