Skip to content

Commit

Permalink
#4222: common GET/SET handler, resource model, and URL paths for filt…
Browse files Browse the repository at this point in the history
…er configs
  • Loading branch information
sreuland committed Feb 24, 2022
1 parent 01d11c2 commit 3e345ba
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 228 deletions.
148 changes: 148 additions & 0 deletions services/horizon/internal/actions/filter_rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package actions

import (
"context"
"encoding/json"
"net/http"

horizonContext "github.com/stellar/go/services/horizon/internal/context"
"github.com/stellar/go/services/horizon/internal/db2/history"
"github.com/stellar/go/support/render/problem"
)

// standard resource interface for a filter config
type filterResource struct {
Rules map[string]interface{} `json:"rules"`
Enabled bool `json:"enabled"`
Name string `json:"name"`
LastModified int64 `json:"last_modified,omitempty"`
}

type FilterQuery struct {
NAME string `schema:"name" valid:"optional"`
}

type FilterRuleHandler struct{}

func (handler FilterRuleHandler) Get(w http.ResponseWriter, r *http.Request) {
historyQ, err := horizonContext.HistoryQFromRequest(r)
if err != nil {
problem.Render(r.Context(), w, err)
return
}

qp := FilterQuery{}
err = getParams(&qp, r)
if err != nil {
problem.Render(r.Context(), w, err)
return
}

var responsePayload interface{}

if (qp.NAME != "") {
responsePayload, err = handler.findOne(qp.NAME, historyQ, r.Context())
} else {
responsePayload, err = handler.findAll(historyQ, r.Context())
}

if err != nil {
problem.Render(r.Context(), w, err)
}

enc := json.NewEncoder(w)
if err = enc.Encode(responsePayload); err != nil {
problem.Render(r.Context(), w, err)
}
}

func (handler FilterRuleHandler) Set(w http.ResponseWriter, r *http.Request) {
historyQ, err := horizonContext.HistoryQFromRequest(r)
if err != nil {
problem.Render(r.Context(), w, err)
return
}
var filterRequest filterResource
dec := json.NewDecoder(r.Body)
if err = dec.Decode(&filterRequest); err != nil {
p := problem.BadRequest
p.Extras = map[string]interface{}{
"invalid json for filter config": err.Error(),
}
problem.Render(r.Context(), w, err)
return
}

//TODO, consider type specific schema validation of the json in filterRequest.Rules based on filterRequest.Name
// if name='asset', verify against an Asset Config Struct
// if name='account', verify against an Account Config Struct

filterConfig := history.FilterConfig{}
filterConfig.Enabled = filterRequest.Enabled
filterConfig.Name = filterRequest.Name

filterRules, err := json.Marshal(filterRequest.Rules)
if err != nil {
p := problem.ServerError
p.Extras = map[string]interface{}{
"reason": "unable to serialize asset filter rules resource from json",
}
problem.Render(r.Context(), w, err)
return
}
filterConfig.Rules = string(filterRules)

if err = historyQ.SetFilterConfig(r.Context(), filterConfig); err != nil {
problem.Render(r.Context(), w, err)
return
}
}

func (handler FilterRuleHandler) findOne(name string, historyQ *history.Q, ctx context.Context) (*filterResource, error) {
filter, err := historyQ.GetFilterByName(ctx,name)
if err != nil {
return nil, err
}
rules, err := handler.rules(filter.Rules)
if err != nil {
return nil, err
}
return handler.resource(filter, rules), nil
}

func (handler FilterRuleHandler) findAll(historyQ *history.Q, ctx context.Context) ([]*filterResource, error){
configs, err := historyQ.GetAllFilters(ctx)
if err != nil {
return nil, err
}
resources := []*filterResource{}
for _, config := range configs {
rules, err := handler.rules(config.Rules)
if err != nil {
return nil, err
}
resources = append(resources, handler.resource(config, rules))
}
return resources, nil
}

func (handler FilterRuleHandler) rules(input string) (map[string]interface{}, error) {
rules := make(map[string]interface{})
if err := json.Unmarshal([]byte(input), &rules); err != nil {
p := problem.ServerError
p.Extras = map[string]interface{}{
"reason": "invalid filter rule json in db",
}
return nil, p
}
return rules, nil
}

func (handler FilterRuleHandler) resource(config history.FilterConfig, rules map[string]interface{}) *filterResource{
return &filterResource{
Rules: rules,
Enabled: config.Enabled,
Name: config.Name,
LastModified: config.LastModified,
}
}
109 changes: 0 additions & 109 deletions services/horizon/internal/actions/filter_rules_account.go

This file was deleted.

108 changes: 0 additions & 108 deletions services/horizon/internal/actions/filter_rules_asset.go

This file was deleted.

19 changes: 8 additions & 11 deletions services/horizon/internal/httpx/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,17 +342,14 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate
r.Internal.Get("/debug/pprof/heap", pprof.Index)
r.Internal.Get("/debug/pprof/profile", pprof.Profile)
if config.EnableIngestionFiltering {
r.Internal.Route("/ingestion/filter/rules", func(r chi.Router) {
r.Route("/asset", func(r chi.Router) {
handler := actions.AssetFilterRuleHandler{}
r.With(historyMiddleware).Put("/", handler.Set)
r.With(historyMiddleware).Get("/", handler.Get)
})
r.Route("/account", func(r chi.Router) {
handler := actions.AccountFilterRuleHandler{}
r.With(historyMiddleware).Put("/", handler.Set)
r.With(historyMiddleware).Get("/", handler.Get)
})
r.Internal.Route("/ingestion/filters", func(r chi.Router) {
// 3 paths
// GetAll: GET /ingestion/filters/ = response of list of {Name, Rules, Enabled, LastModified}
// GetOne: GET /ingestion/filters/{name} = response of {Name, Rules, Enabled, LastModified} with Name='name' or NOT Found err
// Upsert One: POST /ingestion/filters, http body = {Name, Rules, Enabled}, response of {Name, Rules, Enabled, LastModified}
handler := actions.FilterRuleHandler{}
r.With(historyMiddleware).Post("/", handler.Set)
r.With(historyMiddleware).Get("/", handler.Get)
})
}
}

0 comments on commit 3e345ba

Please sign in to comment.