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 }