From 36ad0846219f669a0ee7d4192f2dfaaf94bc4b58 Mon Sep 17 00:00:00 2001 From: Adam Veldhousen Date: Thu, 6 May 2021 23:07:10 -0500 Subject: [PATCH] added insomnia collection and implemented rules API --- insomnia_collection.json | 1 + internal/http.go | 113 ++++++++++++++++++++++-- internal/sqlite.go | 183 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 279 insertions(+), 18 deletions(-) create mode 100755 insomnia_collection.json diff --git a/insomnia_collection.json b/insomnia_collection.json new file mode 100755 index 0000000..cfff662 --- /dev/null +++ b/insomnia_collection.json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2021-05-07T04:06:30.664Z","__export_source":"insomnia.desktop.app:v2021.3.0","resources":[{"_id":"req_6394471040034f6a8c3a93449e876410","parentId":"fld_bda2bbdad96845fea9fe538c7e4a048a","modified":1619972538341,"created":1619972530509,"url":"{{ _.host }}/api/v1/recursors","name":"Get","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1619972530509,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_bda2bbdad96845fea9fe538c7e4a048a","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1619972526538,"created":1619972526538,"name":"Recursors","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1619972526538,"_type":"request_group"},{"_id":"wrk_5991b68142f14461bbf3882561180835","parentId":null,"modified":1619825636355,"created":1619825636355,"name":"Gopherhole","description":"","scope":"collection","_type":"workspace"},{"_id":"req_a40eecf8b70a4bba84b1f285a2d7f64d","parentId":"fld_bda2bbdad96845fea9fe538c7e4a048a","modified":1619973612312,"created":1619972545523,"url":"{{ _.host }}/api/v1/recursors","name":"Add","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n\t\"ipAddress\": \"1.1.1.1\",\n\t\"timeoutMs\": 1500,\n\t\"weight\": 100\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_8d0329c1ba244e7687c379620f735946"}],"authentication":{},"metaSortKey":-1619971835303,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ca42cc3989cb417fa06c1acc0498f771","parentId":"fld_bda2bbdad96845fea9fe538c7e4a048a","modified":1619972601304,"created":1619972592080,"url":"{{ _.host }}/api/v1/recursors/2","name":"Delete","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1619971487700,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_94ea38ea6986413081545a7c481a2cc3","parentId":"fld_9d1538a640f14a9c972f08f4c6fb25de","modified":1619971172020,"created":1619971164588,"url":"{{ _.host }}/api/v1/rules","name":"Get","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1619971140097,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_9d1538a640f14a9c972f08f4c6fb25de","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1619971125050,"created":1619971125050,"name":"Rules","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1619971125050,"_type":"request_group"},{"_id":"req_beae6b2fa406476683c0a52fb8f85dbe","parentId":"fld_9d1538a640f14a9c972f08f4c6fb25de","modified":1620360294388,"created":1619971185131,"url":"{{ _.host }}/api/v1/rules","name":"Add","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"internal route veldhousen.ninja and veldhousen ass.com\",\n\t\"expression\": \"veldhousen\\\\.(ninja|internal)\",\n\t\"ttl\": 600,\n\t\"answer\": {\n\t\t\"type\": \"A\",\n\t\t\"value\": \"192.168.1.15\"\n\t},\n\t\"weight\": 1001,\n\t\"enabled\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_ff6dee28fb524f0788aefea050845677"}],"authentication":{},"metaSortKey":-1619971140090.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_7a526276efba486d90d8a1a01c23decb","parentId":"fld_9d1538a640f14a9c972f08f4c6fb25de","modified":1619972507535,"created":1619971437643,"url":"{{ _.host }}/api/v1/rules/20","name":"Update","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"ttl\": 3600,\n\t\"weight\": 100,\n\t\"enabled\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_68af4850a9794162b0ee3bfb497db2c9"}],"authentication":{},"metaSortKey":-1619971140084.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_dc53e30083f645b99e356e86bb2577a1","parentId":"fld_9d1538a640f14a9c972f08f4c6fb25de","modified":1620360093944,"created":1619971140047,"url":"{{ _.host }}/api/v1/rules/1","name":"Delete","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1619971140047,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3956a5e844194fcf81c824dd50a061e0","parentId":"fld_bca9251441844ebf9435ec13d0c7d39b","modified":1620358914349,"created":1620358907290,"url":"{{ _.host }}/api/v1/rules/lists","name":"Get","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1619971140097,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_bca9251441844ebf9435ec13d0c7d39b","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1620358907273,"created":1620358907273,"name":"Rule Lists","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1619971119837.5,"_type":"request_group"},{"_id":"req_854c6c50a62144ceb9f4db998ad75dda","parentId":"fld_bca9251441844ebf9435ec13d0c7d39b","modified":1620358919271,"created":1620358907293,"url":"{{ _.host }}/api/v1/rules/lists","name":"Add","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"internal route veldhousen.ninja and veldhousen.com\",\n\t\"expression\": \"veldhousen\\\\.(ninja|internal)\",\n\t\"ttl\": 600,\n\t\"answer\": {\n\t\t\"type\": \"A\",\n\t\t\"value\": \"192.168.1.15\"\n\t},\n\t\"weight\": 1000,\n\t\"enabled\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_ee4c0cd383c04cbdab73d340c8d65c62"}],"authentication":{},"metaSortKey":-1619971140090.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2e72e4427f4642f490ff25273de2fd6f","parentId":"fld_bca9251441844ebf9435ec13d0c7d39b","modified":1620358925632,"created":1620358907297,"url":"{{ _.host }}/api/v1/rules/lists/20","name":"Update","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"ttl\": 3600,\n\t\"weight\": 100,\n\t\"enabled\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_68af4850a9794162b0ee3bfb497db2c9"}],"authentication":{},"metaSortKey":-1619971140084.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_df7699bee6dd45dea413962412f0eb3d","parentId":"fld_bca9251441844ebf9435ec13d0c7d39b","modified":1620358965461,"created":1620358942467,"url":"{{ _.host }}/api/v1/rules/lists/reload/20","name":"Reload","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"ttl\": 3600,\n\t\"weight\": 100,\n\t\"enabled\": true\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_68af4850a9794162b0ee3bfb497db2c9"}],"authentication":{},"metaSortKey":-1619971140065.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_74b86fd322a842e383f6a4550b82ec73","parentId":"fld_bca9251441844ebf9435ec13d0c7d39b","modified":1620358930727,"created":1620358907283,"url":"{{ _.host }}/api/v1/rules/lists/20","name":"Delete","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1619971140047,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b374cf1960b54bf8b88721a51f8d2f98","parentId":"fld_5f480b5d526b4a20b48d75b454596c10","modified":1619971118286,"created":1619825680512,"url":"{{ _.host }}/api/v1/metrics/log","name":"Get Log","description":"","method":"GET","body":{},"parameters":[{"name":"start","value":"","description":"","id":"pair_74a66cf5e32e4c7aba2031eaa553a9bc"},{"name":"end","value":"","description":"","id":"pair_0b85423764ca444d8aa76a8db3581b16"},{"name":"filter","value":"","description":"","id":"pair_7e691ac94f5b48e38a7c3c4d752e28a1"}],"headers":[{"name":"Accept","value":"application/json","description":"","id":"pair_979f0da36371448abaf18b79eca10270"}],"authentication":{},"metaSortKey":-1619446186597.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_5f480b5d526b4a20b48d75b454596c10","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1619971114625,"created":1619971114625,"name":"Metrics","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1619971114625,"_type":"request_group"},{"_id":"req_64e457d80bfe459eb8d49864eb89cbad","parentId":"fld_5f480b5d526b4a20b48d75b454596c10","modified":1619971117087,"created":1619835097295,"url":"{{ _.host }}/api/v1/metrics/stats","name":"Get Stats","description":"","method":"GET","body":{},"parameters":[{"name":"start","value":"","description":"","id":"pair_74a66cf5e32e4c7aba2031eaa553a9bc"},{"name":"end","value":"","description":"","id":"pair_0b85423764ca444d8aa76a8db3581b16"},{"name":"key","value":"domain","description":"","id":"pair_7e691ac94f5b48e38a7c3c4d752e28a1"},{"name":"interval","value":"30","description":"","id":"pair_5dd18b4f7f8a4edc9bb45eb7ff9691e3"}],"headers":[{"name":"Accept","value":"application/json","description":"","id":"pair_979f0da36371448abaf18b79eca10270"}],"authentication":{},"metaSortKey":-1619446186547.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_01f29a2548fc2f1469662bfbdcc069dc507d904d","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1619825708885,"created":1619825636489,"name":"Base Environment","data":{"host":"http://localhost:8080"},"dataPropertyOrder":{"&":["host"]},"color":null,"isPrivate":false,"metaSortKey":1619825636489,"_type":"environment"},{"_id":"jar_01f29a2548fc2f1469662bfbdcc069dc507d904d","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1619825636490,"created":1619825636490,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_44aed44fe3534ee3a7123b2f9d31281f","parentId":"wrk_5991b68142f14461bbf3882561180835","modified":1619825636361,"created":1619825636361,"fileName":"Gopherhole","contents":"","contentType":"yaml","_type":"api_spec"}]} \ No newline at end of file diff --git a/internal/http.go b/internal/http.go index 1ba42e7..e1fc54f 100644 --- a/internal/http.go +++ b/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") diff --git a/internal/sqlite.go b/internal/sqlite.go index afae95c..1a46c29 100644 --- a/internal/sqlite.go +++ b/internal/sqlite.go @@ -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 {