You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

334 lines
7.0 KiB

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", "application/json"))
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.Get("/rules", RestHandler(a.getRules).ToHF())
r.Put("/rules", RestHandler(a.createRule).ToHF())
r.Delete("/rules/{id:[0-9]+}", RestHandler(a.deleteRule).ToHF())
// r.Put("/rules/lists", a.addRulelist)
// r.Get("/rules/lists", a.getRuleLists)
// r.Delete("/rules/lists/{id}", a.deleteRuleList)
// r.Post("/rules/lists/reload/{id}", a.reloadRuleLists)
// r.Delete("/cache/purgeall", RestHandler(a.purgeAll).ToHF())
// r.Delete("/cache/purge", a.purgeKey)
// r.Get("/cache", a.getCacheContents)
})
return a
}
func (a *adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.h.ServeHTTP(w, r)
}
func (a *adminHandler) deleteRule(r *http.Request) (*RestResponse, error) {
ruleIdParam := chi.URLParam(r, "id")
ruleId, err := strconv.Atoi(ruleIdParam)
if err != nil {
return &RestResponse{
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: false,
Payload: "Invalid rule ID",
},
}, nil
}
if err := a.Storage.DeleteRule(ruleId); err != nil {
return &RestResponse{
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: false,
Payload: err.Error(),
},
}, nil
}
return &RestResponse{
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: true,
Payload: ruleId,
},
}, nil
}
func (a *adminHandler) createRule(r *http.Request) (*RestResponse, error) {
var rr RuleRow
if err := json.NewDecoder(r.Body).Decode(&rr); err != nil {
return &RestResponse{
Status: http.StatusUnprocessableEntity,
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: false,
Payload: err.Error(),
},
}, nil
}
if err := a.Storage.AddRule(rr); err != nil {
return &RestResponse{
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: false,
Payload: err.Error(),
},
}, nil
}
return &RestResponse{
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: true,
},
}, nil
}
func (a *adminHandler) getRules(r *http.Request) (*RestResponse, error) {
results, err := a.Storage.GetRules()
if err != nil {
return nil, err
}
if len(results) <= 0 {
results = []RuleRow{}
}
return &RestResponse{
Payload: struct {
Success bool "json:\"success\""
Payload interface{} "json:\"payload\""
}{
Success: true,
Payload: results,
},
}, nil
}
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)
}
}