added insomnia collection and implemented rules API
parent
9cb717cf5c
commit
36ad084621
File diff suppressed because one or more lines are too long
113
internal/http.go
113
internal/http.go
|
|
@ -34,7 +34,7 @@ func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
|||
handler.Use(middleware.RequestID)
|
||||
handler.Use(middleware.RealIP)
|
||||
handler.Use(middleware.Logger)
|
||||
handler.Use(middleware.AllowContentType("application/json; utf-8"))
|
||||
handler.Use(middleware.AllowContentType("application/json; utf-8", "application/json"))
|
||||
handler.Use(middleware.Timeout(time.Second * 10))
|
||||
|
||||
handler.Use(cors.Handler(cors.Options{
|
||||
|
|
@ -50,16 +50,17 @@ func NewAdminHandler(c Cache, s Storage, re *RuleEngine) http.Handler {
|
|||
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.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", a.reloadRuleLists)
|
||||
// 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
|
||||
|
|
@ -70,6 +71,102 @@ 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")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
|
@ -15,12 +16,14 @@ const ISO8601 = "2006-01-02 15:04:05.999"
|
|||
type Storage interface {
|
||||
io.Closer
|
||||
Open() error
|
||||
// AddRecursor() error
|
||||
// DeleteRecursors(id) error
|
||||
// GetRecursors(net.IP) ([]string,error)
|
||||
// AddRule(Rule) error
|
||||
// DeleteRule(int) error
|
||||
// GetRules() ([]Rule,error)
|
||||
AddRecursors(net.IP, int, int, int) error
|
||||
DeleteRecursors(int) error
|
||||
GetRecursors() ([]RecursorRow, error)
|
||||
// UpdateRule(RuleRow) error
|
||||
AddRule(RuleRow) error
|
||||
GetRule(int) (RuleRow, error)
|
||||
GetRules() ([]RuleRow, error)
|
||||
DeleteRule(int) error
|
||||
Log(QueryLog) error
|
||||
GetLog(GetLogInput) ([]QueryLog, error)
|
||||
GetLogAggregate(LogAggregateInput) ([]LogAggregateDataPoint, error)
|
||||
|
|
@ -31,6 +34,58 @@ type Sqlite struct {
|
|||
*sql.DB
|
||||
}
|
||||
|
||||
func (ss *Sqlite) GetRecursors() ([]RecursorRow, error) {
|
||||
sql := `
|
||||
SELECT id, ipAddress, timeoutMs, weight FROM recursors ORDER BY weight ASC;
|
||||
`
|
||||
|
||||
rows, err := ss.Query(sql)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute select for recursors: %w", err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var results []RecursorRow
|
||||
for rows.Next() {
|
||||
var row RecursorRow
|
||||
|
||||
if err := rows.Scan(&row.ID, &row.IpAddress, &row.TimeoutMs, &row.Weight); err != nil {
|
||||
return nil, fmt.Errorf("could not read row: %w", err)
|
||||
}
|
||||
|
||||
results = append(results, row)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (ss *Sqlite) DeleteRecursors(id int) error {
|
||||
sql := `DELETE FROM recursors WHERE id = ?;`
|
||||
if _, err := ss.Exec(sql, id); err != nil {
|
||||
return fmt.Errorf("Could not delete recursor: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RecursorRow struct {
|
||||
ID int `json:"id"`
|
||||
IpAddress string `json:"ipAddress"`
|
||||
TimeoutMs int `json:"timeoutMs"`
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
func (ss *Sqlite) AddRecursors(ip net.IP, port, timeout, weight int) error {
|
||||
sql := `INSERT INTO recursors (ipAddress, timeoutMs, weight) VALUES (?, ?, ?);`
|
||||
|
||||
if _, err := ss.Exec(sql, fmt.Sprintf("%s:%d", ip.String(), port), timeout, weight); err != nil {
|
||||
return fmt.Errorf("could not insert recursor: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetLogInput struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
|
|
@ -39,6 +94,92 @@ type GetLogInput struct {
|
|||
Page int
|
||||
}
|
||||
|
||||
type RuleRow struct {
|
||||
ID int `json:"id"`
|
||||
Weight int `json:"weight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Created time.Time `json:"created"`
|
||||
Rule
|
||||
}
|
||||
|
||||
func (ss *Sqlite) AddRule(rr RuleRow) error {
|
||||
sql := `INSERT INTO rules (name, expression, answerType, answerValue, ttl, weight, enabled, created)
|
||||
VALUES (?, ? , ?, ?, ?, ?, 1, ?);`
|
||||
|
||||
if _, err := ss.Exec(sql, rr.Name, rr.Value, rr.Answer.Type, rr.Answer.Value, rr.TTL, rr.Weight, time.Now().UTC().Format(ISO8601)); err != nil {
|
||||
return fmt.Errorf("could not delete rule: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *Sqlite) GetRule(ruleId int) (RuleRow, error) {
|
||||
sql := `SELECT id, name, expression, answerType, answerValue, ttl, weight, enabled, created FROM rules WHERE id = ?;`
|
||||
|
||||
var rr RuleRow
|
||||
row := ss.QueryRow(sql, ruleId)
|
||||
|
||||
var createdTime string
|
||||
if err := row.Scan(&rr.ID, &rr.Name, &rr.Value, &rr.Answer.Type, &rr.Answer.Value, &rr.TTL, &rr.Weight, &rr.Enabled, &createdTime); err != nil {
|
||||
return rr, err
|
||||
}
|
||||
|
||||
var err error
|
||||
rr.Created, err = time.Parse(ISO8601, createdTime)
|
||||
if err != nil {
|
||||
return rr, fmt.Errorf("could not parse time: %w", err)
|
||||
}
|
||||
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
func (ss *Sqlite) DeleteRule(ruleId int) error {
|
||||
if _, err := ss.Exec(`DELETE FROM rules WHERE id = ?;`, ruleId); err != nil {
|
||||
return fmt.Errorf("could not delete rule: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *Sqlite) GetRules() ([]RuleRow, error) {
|
||||
sql := `SELECT id, name, expression, answerType, answerValue, ttl, weight, enabled, created FROM rules ORDER BY weight ASC;`
|
||||
|
||||
rows, err := ss.Query(sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []RuleRow
|
||||
for rows.Next() {
|
||||
var rule RuleRow
|
||||
var createdTime string
|
||||
|
||||
if err := rows.Scan(
|
||||
&rule.ID,
|
||||
&rule.Name,
|
||||
&rule.Value,
|
||||
&rule.Answer.Type,
|
||||
&rule.Answer.Value,
|
||||
&rule.TTL,
|
||||
&rule.Weight,
|
||||
&rule.Enabled,
|
||||
&createdTime,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("could not read from db: %w", err)
|
||||
}
|
||||
|
||||
rule.Created, err = time.Parse(ISO8601, createdTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results = append(results, rule)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (ss *Sqlite) GetLog(in GetLogInput) ([]QueryLog, error) {
|
||||
if in.Limit <= 0 {
|
||||
in.Limit = 100
|
||||
|
|
@ -67,7 +208,6 @@ func (ss *Sqlite) GetLog(in GetLogInput) ([]QueryLog, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var ql []QueryLog
|
||||
|
|
@ -159,16 +299,17 @@ func (ss *Sqlite) GetLogAggregate(la LogAggregateInput) ([]LogAggregateDataPoint
|
|||
|
||||
sql = fmt.Sprintf(sql, column, timeWindow, column, timeWindow)
|
||||
|
||||
result, err := ss.Query(sql)
|
||||
rows, err := ss.Query(sql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []LogAggregateDataPoint
|
||||
for result.Next() {
|
||||
for rows.Next() {
|
||||
var ladp LogAggregateDataPoint
|
||||
var timeInterval int64
|
||||
if err := result.Scan(
|
||||
if err := rows.Scan(
|
||||
&ladp.Header,
|
||||
&ladp.AverageTotalTime,
|
||||
&ladp.Count,
|
||||
|
|
@ -244,6 +385,28 @@ func initTable(db *sql.DB) error {
|
|||
recurseUpStreamIP TEXT,
|
||||
status TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rules (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
expression TEXT NOT NULL,
|
||||
answerType TEXT NOT NULL,
|
||||
answerValue TEXT NOT NULL,
|
||||
ttl INT NOT NULL,
|
||||
weight INT NOT NULL,
|
||||
enabled INT NOT NULL,
|
||||
created TEXT NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_rules_name ON rules (name);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_rules_expression ON rules (expression);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recursors (
|
||||
id INTEGER PRIMARY KEY,
|
||||
ipAddress TEXT NOT NULL,
|
||||
timeoutMs INT NOT NULL,
|
||||
weight INT NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_recursors_ipAddress ON recursors (ipAddress);
|
||||
`
|
||||
|
||||
if _, err := db.Exec(sql); err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue