diff --git a/docs/docs.go b/docs/docs.go index 1c505a8..948318b 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -41,27 +41,49 @@ var doc = `{ "type": "integer", "description": "The limit indicates the maximum number of items to return", "name": "limit", - "in": "query", - "required": true + "in": "query" }, { "type": "integer", "description": "The offset indicates the starting position of the query in relation to the complete set of unpaginated items", "name": "offset", - "in": "query", - "required": true + "in": "query" }, { "type": "string", - "description": "Filter by webhook_id", + "description": "Filter by webhook_id field", "name": "webhook_id", "in": "query" }, { "type": "string", - "description": "Filter by status", + "description": "Filter by status field", "name": "status", "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than this value", + "name": "created_at.gt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than or equal to this value", + "name": "created_at.gte", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than this value", + "name": "created_at.lt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than or equal to this value", + "name": "created_at.lte", + "in": "query" } ], "responses": { @@ -221,15 +243,13 @@ var doc = `{ "type": "integer", "description": "The limit indicates the maximum number of items to return", "name": "limit", - "in": "query", - "required": true + "in": "query" }, { "type": "integer", "description": "The offset indicates the starting position of the query in relation to the complete set of unpaginated items", "name": "offset", - "in": "query", - "required": true + "in": "query" }, { "type": "string", @@ -244,10 +264,34 @@ var doc = `{ "in": "query" }, { - "type": "string", + "type": "boolean", "description": "Filter by success", "name": "success", "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than this value", + "name": "created_at.gt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than or equal to this value", + "name": "created_at.gte", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than this value", + "name": "created_at.lt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than or equal to this value", + "name": "created_at.lte", + "in": "query" } ], "responses": { @@ -326,15 +370,43 @@ var doc = `{ "type": "integer", "description": "The limit indicates the maximum number of items to return", "name": "limit", - "in": "query", - "required": true + "in": "query" }, { "type": "integer", "description": "The offset indicates the starting position of the query in relation to the complete set of unpaginated items", "name": "offset", - "in": "query", - "required": true + "in": "query" + }, + { + "type": "boolean", + "description": "Filter by active field", + "name": "active", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than this value", + "name": "created_at.gt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than or equal to this value", + "name": "created_at.gte", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than this value", + "name": "created_at.lt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than or equal to this value", + "name": "created_at.lte", + "in": "query" } ], "responses": { diff --git a/docs/swagger.json b/docs/swagger.json index ab35c91..e298bca 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -25,27 +25,49 @@ "type": "integer", "description": "The limit indicates the maximum number of items to return", "name": "limit", - "in": "query", - "required": true + "in": "query" }, { "type": "integer", "description": "The offset indicates the starting position of the query in relation to the complete set of unpaginated items", "name": "offset", - "in": "query", - "required": true + "in": "query" }, { "type": "string", - "description": "Filter by webhook_id", + "description": "Filter by webhook_id field", "name": "webhook_id", "in": "query" }, { "type": "string", - "description": "Filter by status", + "description": "Filter by status field", "name": "status", "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than this value", + "name": "created_at.gt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than or equal to this value", + "name": "created_at.gte", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than this value", + "name": "created_at.lt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than or equal to this value", + "name": "created_at.lte", + "in": "query" } ], "responses": { @@ -205,15 +227,13 @@ "type": "integer", "description": "The limit indicates the maximum number of items to return", "name": "limit", - "in": "query", - "required": true + "in": "query" }, { "type": "integer", "description": "The offset indicates the starting position of the query in relation to the complete set of unpaginated items", "name": "offset", - "in": "query", - "required": true + "in": "query" }, { "type": "string", @@ -228,10 +248,34 @@ "in": "query" }, { - "type": "string", + "type": "boolean", "description": "Filter by success", "name": "success", "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than this value", + "name": "created_at.gt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than or equal to this value", + "name": "created_at.gte", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than this value", + "name": "created_at.lt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than or equal to this value", + "name": "created_at.lte", + "in": "query" } ], "responses": { @@ -310,15 +354,43 @@ "type": "integer", "description": "The limit indicates the maximum number of items to return", "name": "limit", - "in": "query", - "required": true + "in": "query" }, { "type": "integer", "description": "The offset indicates the starting position of the query in relation to the complete set of unpaginated items", "name": "offset", - "in": "query", - "required": true + "in": "query" + }, + { + "type": "boolean", + "description": "Filter by active field", + "name": "active", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than this value", + "name": "created_at.gt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is greater than or equal to this value", + "name": "created_at.gte", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than this value", + "name": "created_at.lt", + "in": "query" + }, + { + "type": "string", + "description": "Return results where the created_at field is less than or equal to this value", + "name": "created_at.lte", + "in": "query" } ], "responses": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 183b4f3..69089fe 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -129,22 +129,39 @@ paths: - description: The limit indicates the maximum number of items to return in: query name: limit - required: true type: integer - description: The offset indicates the starting position of the query in relation to the complete set of unpaginated items in: query name: offset - required: true type: integer - - description: Filter by webhook_id + - description: Filter by webhook_id field in: query name: webhook_id type: string - - description: Filter by status + - description: Filter by status field in: query name: status type: string + - description: Return results where the created_at field is greater than this + value + in: query + name: created_at.gt + type: string + - description: Return results where the created_at field is greater than or + equal to this value + in: query + name: created_at.gte + type: string + - description: Return results where the created_at field is less than this value + in: query + name: created_at.lt + type: string + - description: Return results where the created_at field is less than or equal + to this value + in: query + name: created_at.lte + type: string produces: - application/json responses: @@ -248,13 +265,11 @@ paths: - description: The limit indicates the maximum number of items to return in: query name: limit - required: true type: integer - description: The offset indicates the starting position of the query in relation to the complete set of unpaginated items in: query name: offset - required: true type: integer - description: Filter by webhook_id in: query @@ -267,6 +282,25 @@ paths: - description: Filter by success in: query name: success + type: boolean + - description: Return results where the created_at field is greater than this + value + in: query + name: created_at.gt + type: string + - description: Return results where the created_at field is greater than or + equal to this value + in: query + name: created_at.gte + type: string + - description: Return results where the created_at field is less than this value + in: query + name: created_at.lt + type: string + - description: Return results where the created_at field is less than or equal + to this value + in: query + name: created_at.lte type: string produces: - application/json @@ -318,14 +352,35 @@ paths: - description: The limit indicates the maximum number of items to return in: query name: limit - required: true type: integer - description: The offset indicates the starting position of the query in relation to the complete set of unpaginated items in: query name: offset - required: true type: integer + - description: Filter by active field + in: query + name: active + type: boolean + - description: Return results where the created_at field is greater than this + value + in: query + name: created_at.gt + type: string + - description: Return results where the created_at field is greater than or + equal to this value + in: query + name: created_at.gte + type: string + - description: Return results where the created_at field is less than this value + in: query + name: created_at.lt + type: string + - description: Return results where the created_at field is less than or equal + to this value + in: query + name: created_at.lte + type: string produces: - application/json responses: diff --git a/go.mod b/go.mod index 810ba9d..56f855f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/DATA-DOG/go-txdb v0.1.3 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/allisson/go-env v0.3.0 - github.com/go-chi/chi/v5 v5.0.0 + github.com/go-chi/chi/v5 v5.0.1 github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/golang-migrate/migrate/v4 v4.14.1 github.com/google/uuid v1.2.0 diff --git a/go.sum b/go.sum index 3b925d2..f2cd9b2 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/chi/v5 v5.0.0 h1:DBPx88FjZJH3FsICfDAfIfnb7XxKIYVGG6lOPlhENAg= -github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-chi/chi/v5 v5.0.1 h1:ALxjCrTf1aflOlkhMnCUP86MubbWFrzB3gkRPReLpTo= +github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/http/handler/delivery.go b/http/handler/delivery.go index f212e8c..a59b08d 100644 --- a/http/handler/delivery.go +++ b/http/handler/delivery.go @@ -27,20 +27,19 @@ type Delivery struct { // @Tags deliveries // @Accept json // @Produce json -// @Param limit query int true "The limit indicates the maximum number of items to return" -// @Param offset query int true "The offset indicates the starting position of the query in relation to the complete set of unpaginated items" -// @Param webhook_id query string false "Filter by webhook_id" -// @Param status query string false "Filter by status" +// @Param limit query int false "The limit indicates the maximum number of items to return" +// @Param offset query int false "The offset indicates the starting position of the query in relation to the complete set of unpaginated items" +// @Param webhook_id query string false "Filter by webhook_id field" +// @Param status query string false "Filter by status field" +// @Param created_at.gt query string false "Return results where the created_at field is greater than this value" +// @Param created_at.gte query string false "Return results where the created_at field is greater than or equal to this value" +// @Param created_at.lt query string false "Return results where the created_at field is less than this value" +// @Param created_at.lte query string false "Return results where the created_at field is less than or equal to this value" // @Success 200 {object} deliveryList // @Failure 500 {object} errorResponse // @Router /deliveries [get] func (d Delivery) List(w http.ResponseWriter, r *http.Request) { - listOptions, err := makeListOptions(r, []string{"webhook_id", "status"}) - if err != nil { - er := errorResponses["internal_server_error"] - makeErrorResponse(w, &er, d.logger) - return - } + listOptions := makeListOptions(r, []string{"webhook_id", "status", "created_at.gt", "created_at.gte", "created_at.lt", "created_at.lte"}) listOptions.OrderBy = "created_at" listOptions.Order = "desc" diff --git a/http/handler/delivery_attempt.go b/http/handler/delivery_attempt.go index aff7582..8ee5a11 100644 --- a/http/handler/delivery_attempt.go +++ b/http/handler/delivery_attempt.go @@ -27,21 +27,20 @@ type DeliveryAttempt struct { // @Tags delivery-attempts // @Accept json // @Produce json -// @Param limit query int true "The limit indicates the maximum number of items to return" -// @Param offset query int true "The offset indicates the starting position of the query in relation to the complete set of unpaginated items" +// @Param limit query int false "The limit indicates the maximum number of items to return" +// @Param offset query int false "The offset indicates the starting position of the query in relation to the complete set of unpaginated items" // @Param webhook_id query string false "Filter by webhook_id" // @Param delivery_id query string false "Filter by delivery_id" -// @Param success query string false "Filter by success" +// @Param success query boolean false "Filter by success" +// @Param created_at.gt query string false "Return results where the created_at field is greater than this value" +// @Param created_at.gte query string false "Return results where the created_at field is greater than or equal to this value" +// @Param created_at.lt query string false "Return results where the created_at field is less than this value" +// @Param created_at.lte query string false "Return results where the created_at field is less than or equal to this value" // @Success 200 {object} deliveryAttemptList // @Failure 500 {object} errorResponse // @Router /delivery-attempts [get] func (d DeliveryAttempt) List(w http.ResponseWriter, r *http.Request) { - listOptions, err := makeListOptions(r, []string{"webhook_id", "delivery_id", "success"}) - if err != nil { - er := errorResponses["internal_server_error"] - makeErrorResponse(w, &er, d.logger) - return - } + listOptions := makeListOptions(r, []string{"webhook_id", "delivery_id", "success", "created_at.gt", "created_at.gte", "created_at.lt", "created_at.lte"}) listOptions.OrderBy = "created_at" listOptions.Order = "desc" diff --git a/http/handler/util.go b/http/handler/util.go index 323352d..ca79f9f 100644 --- a/http/handler/util.go +++ b/http/handler/util.go @@ -6,13 +6,27 @@ import ( "fmt" "io" "net/http" - "strconv" + "time" "github.com/allisson/postmand" validation "github.com/go-ozzo/ozzo-validation/v4" "go.uber.org/zap" ) +type requestFilters struct { + Limit int `json:"limit,string"` + Offset int `json:"offset,string"` + Active bool `json:"active,string"` + Success bool `json:"success,string"` + WebhookID postmand.ID `json:"webhook_id"` + DeliveryID postmand.ID `json:"delivery_id"` + Status string `json:"status"` + CreatedAtGt time.Time `json:"created_at.gt"` + CreatedAtGte time.Time `json:"created_at.gte"` + CreatedAtLt time.Time `json:"created_at.lt"` + CreatedAtLte time.Time `json:"created_at.lte"` +} + func makeResponse(w http.ResponseWriter, body []byte, statusCode int, contentType string, logger *zap.Logger) { w.Header().Set("Content-Type", fmt.Sprintf("%s; charset=utf-8", contentType)) w.WriteHeader(statusCode) @@ -70,39 +84,47 @@ func readBodyJSON(r *http.Request, into interface{}, logger *zap.Logger) *errorR return nil } -func makeListOptions(r *http.Request, filters []string) (postmand.RepositoryListOptions, error) { - listOptions := postmand.RepositoryListOptions{} - +func requestBind(r *http.Request, into interface{}) error { if err := r.ParseForm(); err != nil { - return listOptions, err + return err } - // Parse limit and offset - limit := 50 - offset := 0 - if r.Form.Get("limit") != "" { - v, err := strconv.Atoi(r.Form.Get("limit")) - if err == nil && v <= limit { - limit = v - } + m := make(map[string]string) + for key := range r.Form { + m[key] = r.Form.Get(key) } - if r.Form.Get("offset") != "" { - v, err := strconv.Atoi(r.Form.Get("offset")) - if err == nil { - offset = v - } + rawJSON, err := json.Marshal(m) + if err != nil { + return err + } + return json.Unmarshal(rawJSON, into) +} + +func makeListOptions(r *http.Request, filters []string) postmand.RepositoryListOptions { + listOptions := postmand.RepositoryListOptions{ + Limit: 50, + Offset: 0, + } + rf := requestFilters{} + + if err := requestBind(r, &rf); err != nil { + return listOptions + } + + if rf.Limit != 0 { + listOptions.Limit = rf.Limit } - listOptions.Limit = limit - listOptions.Offset = offset + listOptions.Offset = rf.Offset // Parse filters f := make(map[string]interface{}) for _, filter := range filters { - if r.Form.Get(filter) != "" { - f[filter] = r.Form.Get(filter) + filterValue := r.Form.Get(filter) + if filterValue != "" { + f[filter] = filterValue } } listOptions.Filters = f - return listOptions, nil + return listOptions } diff --git a/http/handler/util_test.go b/http/handler/util_test.go new file mode 100644 index 0000000..726e950 --- /dev/null +++ b/http/handler/util_test.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRequestBind(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + q := req.URL.Query() + q.Add("limit", "10") + q.Add("offset", "5") + q.Add("active", "true") + q.Add("success", "true") + q.Add("webhook_id", "7fe789b4-dec6-4eab-8144-c50e95b866ee") + q.Add("delivery_id", "f5eedad9-76b8-4262-b2b6-bdc3771a80b2") + q.Add("status", "pending") + q.Add("created_at.gt", "2021-03-08T20:50:08.353038Z") + q.Add("created_at.gte", "2021-03-08T20:50:08.353038Z") + q.Add("created_at.lt", "2021-03-08T20:50:08.353038Z") + q.Add("created_at.lte", "2021-03-08T20:50:08.353038Z") + req.URL.RawQuery = q.Encode() + rf := requestFilters{} + err := requestBind(req, &rf) + assert.Nil(t, err) +} diff --git a/http/handler/webhook.go b/http/handler/webhook.go index 0545552..a5084e1 100644 --- a/http/handler/webhook.go +++ b/http/handler/webhook.go @@ -27,18 +27,18 @@ type Webhook struct { // @Tags webhooks // @Accept json // @Produce json -// @Param limit query int true "The limit indicates the maximum number of items to return" -// @Param offset query int true "The offset indicates the starting position of the query in relation to the complete set of unpaginated items" +// @Param limit query int false "The limit indicates the maximum number of items to return" +// @Param offset query int false "The offset indicates the starting position of the query in relation to the complete set of unpaginated items" +// @Param active query boolean false "Filter by active field" +// @Param created_at.gt query string false "Return results where the created_at field is greater than this value" +// @Param created_at.gte query string false "Return results where the created_at field is greater than or equal to this value" +// @Param created_at.lt query string false "Return results where the created_at field is less than this value" +// @Param created_at.lte query string false "Return results where the created_at field is less than or equal to this value" // @Success 200 {object} webhookList // @Failure 500 {object} errorResponse // @Router /webhooks [get] func (wh Webhook) List(w http.ResponseWriter, r *http.Request) { - listOptions, err := makeListOptions(r, []string{}) - if err != nil { - er := errorResponses["internal_server_error"] - makeErrorResponse(w, &er, wh.logger) - return - } + listOptions := makeListOptions(r, []string{"active", "created_at.gt", "created_at.gte", "created_at.lt", "created_at.lte"}) listOptions.OrderBy = "name" listOptions.Order = "asc" diff --git a/repository/query.go b/repository/query.go index 5145acf..74a418f 100644 --- a/repository/query.go +++ b/repository/query.go @@ -2,6 +2,7 @@ package repository import ( "log" + "strings" "github.com/allisson/postmand" "github.com/huandu/go-sqlbuilder" @@ -21,7 +22,24 @@ func listQuery(tableName string, listOptions postmand.RepositoryListOptions) (st sb := sqlbuilder.PostgreSQL.NewSelectBuilder() sb.Select("*").From(tableName).Limit(listOptions.Limit).Offset(listOptions.Offset) for key, value := range listOptions.Filters { - sb.Where(sb.Equal(key, value)) + if strings.Contains(key, ".") { + split := strings.Split(key, ".") + parsedKey := split[0] + compare := split[1] + switch compare { + case "gt": + sb.Where(sb.GreaterThan(parsedKey, value)) + case "gte": + sb.Where(sb.GreaterEqualThan(parsedKey, value)) + case "lt": + sb.Where(sb.LessThan(parsedKey, value)) + case "lte": + sb.Where(sb.LessEqualThan(parsedKey, value)) + } + } else { + sb.Where(sb.Equal(key, value)) + } + } if listOptions.OrderBy != "" && listOptions.Order != "" { sb.OrderBy(listOptions.OrderBy)