github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/lib/filter/params.go (about)

     1  // Package filter - Content managed by Project Forge, see [projectforge.md] for details.
     2  package filter
     3  
     4  import (
     5  	"fmt"
     6  	"net/url"
     7  	"strings"
     8  
     9  	"github.com/samber/lo"
    10  
    11  	"github.com/kyleu/dbaudit/app/util"
    12  )
    13  
    14  const (
    15  	PageSize = 100
    16  	MaxRows  = 10000
    17  
    18  	SuffixOrder      = ".o"
    19  	SuffixLimit      = ".l"
    20  	SuffixOffset     = ".x"
    21  	SuffixDescending = ".d"
    22  )
    23  
    24  var AllowedColumns = map[string][]string{}
    25  
    26  type Params struct {
    27  	Key       string    `json:"key"`
    28  	Orderings Orderings `json:"orderings,omitempty"`
    29  	Limit     int       `json:"limit,omitempty"`
    30  	Offset    int       `json:"offset,omitempty"`
    31  }
    32  
    33  func ParamsWithDefaultOrdering(key string, params *Params, orderings ...*Ordering) *Params {
    34  	if params == nil {
    35  		return ParamsWithDefaultOrdering(key, &Params{Key: key}, orderings...)
    36  	}
    37  	if len(params.Orderings) == 0 {
    38  		params.Orderings = orderings
    39  	}
    40  	return params
    41  }
    42  
    43  func (p *Params) Sanitize(key string, defaultOrderings ...*Ordering) *Params {
    44  	if p == nil {
    45  		return &Params{Key: key, Orderings: defaultOrderings}
    46  	}
    47  	if p.Limit == 0 || p.Limit > MaxRows {
    48  		p.Limit = PageSize
    49  	}
    50  	if p.Offset < 0 {
    51  		p.Offset = 0
    52  	}
    53  	if len(p.Orderings) == 0 {
    54  		return p.CloneOrdering(defaultOrderings...)
    55  	}
    56  	return p
    57  }
    58  
    59  func (p *Params) WithLimit(n int) *Params {
    60  	p.Limit = n
    61  	return p
    62  }
    63  
    64  func (p *Params) CloneOrdering(orderings ...*Ordering) *Params {
    65  	if p == nil {
    66  		return nil
    67  	}
    68  	return &Params{Key: p.Key, Orderings: orderings, Limit: p.Limit, Offset: p.Offset}
    69  }
    70  
    71  func (p *Params) CloneLimit(limit int) *Params {
    72  	if p == nil {
    73  		return nil
    74  	}
    75  	return &Params{Key: p.Key, Orderings: p.Orderings, Limit: limit, Offset: p.Offset}
    76  }
    77  
    78  func (p *Params) HasNextPage(count int) bool {
    79  	if p == nil || p.Limit == 0 {
    80  		return false
    81  	}
    82  	return count >= (p.Offset + p.Limit)
    83  }
    84  
    85  func (p *Params) NextPage() *Params {
    86  	limit := p.Limit
    87  	if limit == 0 {
    88  		limit = PageSize
    89  	}
    90  	offset := p.Offset + limit
    91  	if offset < 0 {
    92  		offset = 0
    93  	}
    94  	return &Params{Key: p.Key, Orderings: p.Orderings, Limit: p.Limit, Offset: offset}
    95  }
    96  
    97  func (p *Params) HasPreviousPage() bool {
    98  	return p != nil && p.Offset > 0
    99  }
   100  
   101  func (p *Params) PreviousPage() *Params {
   102  	limit := p.Limit
   103  	if limit == 0 {
   104  		limit = PageSize
   105  	}
   106  	offset := p.Offset - limit
   107  	if offset < 0 {
   108  		offset = 0
   109  	}
   110  	return &Params{Key: p.Key, Orderings: p.Orderings, Limit: p.Limit, Offset: offset}
   111  }
   112  
   113  func (p *Params) GetOrdering(col string) *Ordering {
   114  	return lo.FindOrElse(p.Orderings, nil, func(o *Ordering) bool {
   115  		return o.Column == col
   116  	})
   117  }
   118  
   119  func (p *Params) OrderByString() string {
   120  	ret := lo.Map(p.Orderings, func(o *Ordering, _ int) string {
   121  		dir := ""
   122  		if !o.Asc {
   123  			dir = " desc"
   124  		}
   125  		return fmt.Sprintf("%q%s", o.Column, dir)
   126  	})
   127  	return strings.Join(ret, ", ")
   128  }
   129  
   130  func (p *Params) Filtered(available []string, logger util.Logger) *Params {
   131  	if available == nil {
   132  		available = AllowedColumns[p.Key]
   133  	}
   134  	if len(available) == 0 {
   135  		logger.Warnf("no columns available for [%s]", p.Key)
   136  	}
   137  	if len(available) == 1 && available[0] == "*" {
   138  		return p
   139  	}
   140  	if len(p.Orderings) > 0 {
   141  		allowed := Orderings{}
   142  
   143  		lo.ForEach(p.Orderings, func(o *Ordering, _ int) {
   144  			containsCol := lo.ContainsBy(available, func(c string) bool {
   145  				return c == o.Column
   146  			})
   147  			if containsCol {
   148  				allowed = append(allowed, o)
   149  			} else {
   150  				const msg = "no column [%s] for [%s] available in allowed columns [%s]"
   151  				logger.Warnf(msg, o.Column, p.Key, util.StringArrayOxfordComma(available, "and"))
   152  			}
   153  		})
   154  		return &Params{Key: p.Key, Orderings: allowed, Limit: p.Limit, Offset: p.Offset}
   155  	}
   156  	return p
   157  }
   158  
   159  func (p *Params) IsDefault() bool {
   160  	return p.Offset == 0 && p.Limit == 0 && len(p.Orderings) == 0
   161  }
   162  
   163  func (p *Params) String() string {
   164  	ol := ""
   165  	if p.Offset > 0 {
   166  		ol += fmt.Sprintf("%d/", p.Offset)
   167  	}
   168  	if p.Limit > 0 {
   169  		ol += fmt.Sprint(p.Limit)
   170  	}
   171  	ord := lo.Map(p.Orderings, func(o *Ordering, _ int) string {
   172  		return o.String()
   173  	})
   174  	return fmt.Sprintf("%s(%s): %s", p.Key, ol, strings.Join(ord, " / "))
   175  }
   176  
   177  func (p *Params) ToQueryString(u *url.URL) string {
   178  	if p == nil {
   179  		return ""
   180  	}
   181  
   182  	if u == nil {
   183  		return ""
   184  	}
   185  
   186  	ret := url.Values{}
   187  	for k, v := range u.Query() {
   188  		ret[k] = v
   189  	}
   190  
   191  	ret.Del(p.Key + SuffixOrder)
   192  	ret.Del(p.Key + SuffixLimit)
   193  	ret.Del(p.Key + SuffixOffset)
   194  
   195  	lo.ForEach(p.Orderings, func(o *Ordering, _ int) {
   196  		s := o.Column
   197  		if !o.Asc {
   198  			s += SuffixDescending
   199  		}
   200  		ret.Add(p.Key+SuffixOrder, s)
   201  	})
   202  
   203  	if p.Limit != 0 && p.Limit != 1000 {
   204  		ret.Add(p.Key+SuffixLimit, fmt.Sprint(p.Limit))
   205  	}
   206  
   207  	if p.Offset > 0 {
   208  		ret.Add(p.Key+SuffixOffset, fmt.Sprint(p.Offset))
   209  	}
   210  
   211  	return ret.Encode()
   212  }