Skip to content

Commit

Permalink
Merge pull request #22 from morkid/v1.1.10-dev
Browse files Browse the repository at this point in the history
V1.1.10 dev
  • Loading branch information
morkid authored Jan 17, 2025
2 parents d1b164a + a3a23cb commit 6c089f7
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 59 deletions.
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Simple way to paginate [Gorm](https://github.com/go-gorm/gorm) result. **paginat
- [Beego](#beego-example)
- [jQuery DataTable Integration](#jquery-datatable-integration)
- [jQuery Select2 Integration](#jquery-select2-integration)
- [Programmatically Pagination](#programmatically-pagination)
- [Filter format](#filter-format)
- [Customize default configuration](#customize-default-configuration)
- [Override results](#override-results)
Expand Down Expand Up @@ -200,9 +201,10 @@ example paging, sorting and filtering:
```bash
curl -X POST \
-H 'Content-type: application/json' \
-d '{"page":"1","size":"20","sort":"-name","filters":["name","john"]}' \
-d '{"page":1,"size":20,"sort":"-name","filters":["name","john"]}' \
http://localhost:3000/
```
10. You can bypass HTTP Request with [Custom Request](#programmatically-pagination).

## Example usage

Expand Down Expand Up @@ -499,10 +501,46 @@ $('#mySelect').select2({
})
```

### Programmatically Pagination

```go
package main
import (
"github.com/morkid/paginate"
...
)
func main() {
// var db *gorm.DB
pg := paginate.New()
req := &paginate.Request{
Page: 2,
Size: 20,
Sort: "-publish_date",
filters: []interface{}{
[]interface{}{"user.name", "like", "john"},
[]interface{}{"and"},
[]interface{}{"publish_date", ">=", "2025-12-31"},
[]interface{}{"and"},
[]interface{}{"user.active", "=", true},
[]interface{}{"and"},
[]interface{}{"user.last_login", "is not", nil},
}
}
stmt := db.Joins("User").Model(&Article{})
page := pg.With(stmt).
Request(req).
Response(&[]Article{})
}
```


## Filter format

The format of filter param is a json encoded of multidimensional array.
Paginate Filters was inspired by [Frappe Framework API](https://docs.frappe.io/framework/user/en/api/rest#listing-documents). This feature is very powerful to support deep search and keep it safe. The format of filter param is a json encoded of multidimensional array.
Maximum array members is three, first index is `column_name`, second index is `operator` and third index is `values`, you can also pass array to values.

```js
Expand Down Expand Up @@ -544,7 +582,7 @@ Define chain columns with same value separated by comma.
// Example 1
["price,discount", ">", 10]
// Produces:
// WHERE price > 10 OR discount > 25
// WHERE price > 10 OR discount > 10
// Example 2
["deleted_at,expiration_date", null]
Expand Down
86 changes: 42 additions & 44 deletions paginate.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ func (r resContext) Response(res interface{}) Page {
}

result = result.Count(&page.Total).
Limit(causes.Limit).
Offset(causes.Offset)
Limit(int(causes.Limit)).
Offset(int(causes.Offset))

page.RawError = result.Error

Expand Down Expand Up @@ -294,6 +294,10 @@ func parseRequest(r interface{}, config Config) pageRequest {
} else {
if fastHTTPp, isFastHTTPp := r.(*fasthttp.Request); isFastHTTPp {
parsingFastHTTPRequest(fastHTTPp, &pr)
} else {
if request, isRequest := r.(*Request); isRequest {
parsingQueryString(request, &pr)
}
}
}
}
Expand Down Expand Up @@ -332,7 +336,7 @@ func createCauses(p pageRequest) requestQuery {
}

query.Limit = p.Size
query.Offset = (p.Page - int(p.Config.PageStart)) * p.Size
query.Offset = (p.Page - p.Config.PageStart) * p.Size
query.Wheres = wheres
query.WhereString = strings.Join(wheres, " ")
query.Sorts = sorts
Expand All @@ -343,7 +347,7 @@ func createCauses(p pageRequest) requestQuery {

// parsingNetHTTPRequest func
func parsingNetHTTPRequest(r *http.Request, p *pageRequest) {
param := &parameter{}
param := &Request{}
if r.Method == "" {
r.Method = "GET"
}
Expand All @@ -354,7 +358,7 @@ func parsingNetHTTPRequest(r *http.Request, p *pageRequest) {
}
defer r.Body.Close()
if !p.Config.CustomParamEnabled {
var postData parameter
var postData Request
if err := p.Config.JSONUnmarshal(body, &postData); nil == err {
param = &postData
} else {
Expand All @@ -377,12 +381,12 @@ func parsingNetHTTPRequest(r *http.Request, p *pageRequest) {
} else if strings.ToUpper(r.Method) == "GET" {
query := r.URL.Query()
if !p.Config.CustomParamEnabled {
param.Size = query.Get("size")
param.Page = query.Get("page")
param.Size, _ = strconv.ParseInt(query.Get("size"), 10, 64)
param.Page, _ = strconv.ParseInt(query.Get("page"), 10, 64)
param.Sort = query.Get("sort")
param.Order = query.Get("order")
param.Filters = query.Get("filters")
param.Fields = query.Get("fields")
param.Fields = strings.Split(query.Get("fields"), ",")
} else {
generateParams(param, p.Config, func(key string) string {
return query.Get(key)
Expand All @@ -395,11 +399,11 @@ func parsingNetHTTPRequest(r *http.Request, p *pageRequest) {

// parsingFastHTTPRequest func
func parsingFastHTTPRequest(r *fasthttp.Request, p *pageRequest) {
param := &parameter{}
param := &Request{}
if r.Header.IsPost() {
b := r.Body()
if !p.Config.CustomParamEnabled {
var postData parameter
var postData Request
if err := p.Config.JSONUnmarshal(b, &postData); nil == err {
param = &postData
} else {
Expand All @@ -422,12 +426,12 @@ func parsingFastHTTPRequest(r *fasthttp.Request, p *pageRequest) {
} else if r.Header.IsGet() {
query := r.URI().QueryArgs()
if !p.Config.CustomParamEnabled {
param.Size = string(query.Peek("size"))
param.Page = string(query.Peek("page"))
param.Size, _ = strconv.ParseInt(string(query.Peek("size")), 10, 64)
param.Page, _ = strconv.ParseInt(string(query.Peek("page")), 10, 64)
param.Sort = string(query.Peek("sort"))
param.Order = string(query.Peek("order"))
param.Filters = string(query.Peek("filters"))
param.Fields = string(query.Peek("fields"))
param.Fields = strings.Split(string(query.Peek("fields")), ",")
} else {
generateParams(param, p.Config, func(key string) string {
return string(query.Peek(key))
Expand All @@ -438,23 +442,19 @@ func parsingFastHTTPRequest(r *fasthttp.Request, p *pageRequest) {
parsingQueryString(param, p)
}

func parsingQueryString(param *parameter, p *pageRequest) {
if i, e := strconv.Atoi(param.Size); nil == e {
p.Size = i
}

func parsingQueryString(param *Request, p *pageRequest) {
p.Size = param.Size
if p.Size == 0 {
if p.Config.DefaultSize > 0 {
p.Size = int(p.Config.DefaultSize)
p.Size = p.Config.DefaultSize
} else {
p.Size = 10
}
}

if i, e := strconv.Atoi(param.Page); nil == e {
p.Page = i
} else {
p.Page = int(p.Config.PageStart)
p.Page = param.Page
if p.Page < p.Config.PageStart {
p.Page = p.Config.PageStart
}

if param.Sort != "" {
Expand All @@ -481,22 +481,20 @@ func parsingQueryString(param *parameter, p *pageRequest) {
}
}

if param.Fields != "" {
if len(param.Fields) > 0 {
re := regexp.MustCompile(`[^A-z0-9_\.,]+`)
if fields := strings.Split(param.Fields, ","); len(fields) > 0 {
for i := range fields {
fieldByte := re.ReplaceAll([]byte(fields[i]), []byte(""))
if field := string(fieldByte); field != "" {
p.Fields = append(p.Fields, field)
}
for _, field := range param.Fields {
fieldName := re.ReplaceAllString(field, "")
if fieldName != "" {
p.Fields = append(p.Fields, fieldName)
}
}
}

createFilters(param.Filters, p)
}

func generateParams(param *parameter, config Config, getValue func(string) string) {
func generateParams(param *Request, config Config, getValue func(string) string) {
findValue := func(keys []string, defaultKey string) string {
found := false
value := ""
Expand All @@ -514,11 +512,11 @@ func generateParams(param *parameter, config Config, getValue func(string) strin
}

param.Sort = findValue(config.SortParams, "sort")
param.Page = findValue(config.PageParams, "page")
param.Size = findValue(config.SizeParams, "size")
param.Page, _ = strconv.ParseInt(findValue(config.PageParams, "page"), 10, 64)
param.Size, _ = strconv.ParseInt(findValue(config.SizeParams, "size"), 10, 64)
param.Order = findValue(config.OrderParams, "order")
param.Filters = findValue(config.FilterParams, "filters")
param.Fields = findValue(config.FieldsParams, "fields")
param.Fields = strings.Split(findValue(config.FieldsParams, "fields"), ",")
}

func arrayToFilter(arr []interface{}, config Config) pageFilters {
Expand Down Expand Up @@ -556,7 +554,7 @@ func arrayToFilter(arr []interface{}, config Config) pageFilters {
}
} else if k == 1 {
filters.Value = i
if nil == i || reflect.TypeOf(i).Name() == "bool" {
if nil == i {
filters.Operator = "IS"
}
if strings.Contains(filters.Column, ",") {
Expand Down Expand Up @@ -861,13 +859,13 @@ type Page struct {
RawError error `json:"-"`
}

// parameter struct
type parameter struct {
Page string `json:"page"`
Size string `json:"size"`
// Request struct
type Request struct {
Page int64 `json:"page"`
Size int64 `json:"size"`
Sort string `json:"sort"`
Order string `json:"order"`
Fields string `json:"fields"`
Fields []string `json:"fields"`
Filters interface{} `json:"filters"`
}

Expand All @@ -877,14 +875,14 @@ type requestQuery struct {
Wheres []string
Params []interface{}
Sorts []sortOrder
Limit int
Offset int
Limit int64
Offset int64
}

// pageRequest struct
type pageRequest struct {
Size int
Page int
Size int64
Page int64
Sorts []sortOrder
Filters pageFilters
Config Config `json:"-"`
Expand Down
Loading

0 comments on commit 6c089f7

Please sign in to comment.