package internal import ( "encoding/json" "errors" "fmt" "log" "net/http" "strconv" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) type adminHandler struct { Cache Storage *RuleEngine h http.Handler } func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler { handler := chi.NewRouter() a := &adminHandler{ Cache: c, Storage: s, RuleEngine: re, h: handler, } handler.Use(middleware.RequestID) handler.Use(middleware.RealIP) handler.Use(middleware.Logger) handler.Use(middleware.AllowContentType("application/json; utf-8")) handler.Use(middleware.Timeout(time.Second * 10)) handler.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"http://*", "https://*"}, AllowedMethods: []string{"GET", "PUT", "DELETE", "POST", "OPTIONS"}, AllowedHeaders: []string{"Content-Type", "Accept"}, AllowCredentials: false, MaxAge: 300, })) handler.Use(middleware.Recoverer) handler.Route("/api/v1", func(r chi.Router) { r.Get("/metrics/log", RestHandler(a.getLog).ToHF()) r.Get("/metrics/stats", RestHandler(a.getStats).ToHF()) // r.Delete("/cache/purgeall", RestHandler(a.purgeAll).ToHF()) // r.Delete("/cache/purge", a.purgeKey) // r.Get("/cache", a.getCacheContents) // r.Put("/rules", a.createRule) // r.Get("/rules", a.getRules) // r.Delete("/rules/{id}", a.deleteRole) // r.Put("/rules/lists", a.addRulelist) // r.Get("/rules/lists", a.getRuleLists) // r.Delete("/rules/lists/{id}", a.deleteRuleList) // r.Post("/rules/lists/reload", a.reloadRuleLists) }) return a } func (a *adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.h.ServeHTTP(w, r) } func (a *adminHandler) getStats(r *http.Request) (*RestResponse, error) { q := r.URL.Query() startFilter := q.Get("start") endFilter := q.Get("end") key := q.Get("key") intervalSecondsStr := q.Get("interval") var err error start := time.Now().UTC().Add(time.Hour * -86400) end := time.Now().UTC() if startFilter != "" { if start, err = time.Parse(ISO8601, startFilter); err != nil { return nil, err } } if endFilter != "" { if end, err = time.Parse(ISO8601, endFilter); err != nil { return nil, err } } lai := LogAggregateInput{ Start: start, End: end, Column: key, } if intervalSecondsStr != "" { if lai.IntervalSeconds, err = strconv.Atoi(intervalSecondsStr); err != nil { return nil, errors.New("interval query param must be a valid whole number") } } la, err := a.Storage.GetLogAggregate(lai) if err != nil { return nil, err } return &RestResponse{ Status: http.StatusOK, Payload: struct { Success bool `json:"success"` Payload interface{} `json:"payload"` }{ Success: true, Payload: la, }, }, nil } func (a *adminHandler) getLog(r *http.Request) (*RestResponse, error) { q := r.URL.Query() startFilter := q.Get("start") endFilter := q.Get("end") // filter := q.Get("filter") pageStr := q.Get("page") var err error var page int start := time.Now().UTC().Add(time.Hour * -86400) end := time.Now().UTC() if startFilter != "" { if start, err = time.Parse(ISO8601, startFilter); err != nil { return nil, err } } if endFilter != "" { if end, err = time.Parse(ISO8601, endFilter); err != nil { return nil, err } } if pageStr != "" { page, _ = strconv.Atoi(pageStr) } ql, err := a.Storage.GetLog(GetLogInput{ Start: start, End: end, Limit: 250, Page: page, }) if err != nil { return nil, err } return &RestResponse{ Status: http.StatusOK, Payload: struct { Success bool `json:"success"` Payload interface{} `json:"payload"` }{ Success: true, Payload: ql, }, }, nil } type RestResponse struct { Status int Headers http.Header Payload struct { Success bool `json:"success"` Payload interface{} `json:"payload"` } } func (rr *RestResponse) Write(w http.ResponseWriter) error { if rr.Status != 0 && rr.Status != 200 { w.WriteHeader(rr.Status) } for k, v := range rr.Headers { for _, ve := range v { w.Header().Add(k, ve) } } e := json.NewEncoder(w) e.SetIndent("\n", "\t") if err := e.Encode(rr.Payload); err != nil { return fmt.Errorf("could not serialize struct for http response: %w", err) } return nil } type RestHandler func(request *http.Request) (*RestResponse, error) func (rh RestHandler) ToHF() http.HandlerFunc { return func(rw http.ResponseWriter, r *http.Request) { rh.ServeHTTP(rw, r) } } func (rh RestHandler) Error(e error) *RestResponse { return &RestResponse{ Status: http.StatusInternalServerError, Payload: struct { Success bool `json:"success"` Payload interface{} `json:"payload"` }{ Success: false, Payload: e.Error(), }, } } func (r RestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { response, err := r(req) if err != nil { response = r.Error(err) } if err := response.Write(w); err != nil { log.Printf("Error occurred handling rest response: %v", err) } }