From 345adcf4792e288573c803e9a739630a6f2c176f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 1 May 2023 19:25:16 +0200 Subject: [PATCH] Refactoring --- .golangci.yml | 2 +- Dockerfile | 5 ++++- src/{ => main}/api.go | 17 ++++++++++------- src/{ => main}/config.go | 14 +++++++------- src/{ => main}/database.go | 28 ++++++++++++++++++++-------- src/{ => main}/http.go | 13 +++++++++---- src/{ => main}/main.go | 0 7 files changed, 51 insertions(+), 28 deletions(-) rename src/{ => main}/api.go (95%) rename src/{ => main}/config.go (88%) rename src/{ => main}/database.go (84%) rename src/{ => main}/http.go (93%) rename src/{ => main}/main.go (100%) diff --git a/.golangci.yml b/.golangci.yml index 8664e09..cb1b86a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,7 @@ # options for analysis running run: # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 5m + timeout: 10m # output configuration options output: diff --git a/Dockerfile b/Dockerfile index d7be2c4..905c678 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,10 @@ COPY --from=0 /usr/bin/golangci-lint /usr/bin/golangci-lint RUN apk add --no-cache gcc libc-dev WORKDIR /app -COPY src /app/ +COPY src /app/src +COPY go.mod /app/ +COPY go.sum /app/ +COPY .golangci.yml /app/ RUN golangci-lint run RUN go test ./src/... diff --git a/src/api.go b/src/main/api.go similarity index 95% rename from src/api.go rename to src/main/api.go index 81b79a1..2e4ac66 100644 --- a/src/api.go +++ b/src/main/api.go @@ -31,18 +31,21 @@ func (config *Config) queryHistory(entityID string, startTime, endTime time.Time req, err := http.NewRequestWithContext( ctx, http.MethodGet, - config.HomeAssistant.BaseURL+ - "/api/history/period/"+url.QueryEscape(startTime.Format(time.RFC3339))+ - "?filter_entity_id="+entityID+ - "&end_time="+url.QueryEscape(endTime.Format(time.RFC3339)), - nil, + fmt.Sprintf( + "%s/api/history/period/%s?filter_entity_id=%s&end_time=%s", + config.HomeAssistant.BaseURL, + url.QueryEscape(startTime.Format(time.RFC3339)), + entityID, + url.QueryEscape(endTime.Format(time.RFC3339)), + ), + http.NoBody, ) cancel() if err != nil { return HistoryResult{}, err } - req.Header.Add("Authorization", "Bearer "+config.HomeAssistant.ApiKey) + req.Header.Add("Authorization", "Bearer "+config.HomeAssistant.APIKey) client := &http.Client{} resp, err := client.Do(req) @@ -239,7 +242,7 @@ func fillMissing(days []DayData, startTime, endTime time.Time) []DayData { previousDay time.Time defaultDay time.Time previousValue float32 - ret []DayData + ret = make([]DayData, 0, len(days)) currentTime = time.Now() ) diff --git a/src/config.go b/src/main/config.go similarity index 88% rename from src/config.go rename to src/main/config.go index 6b7011f..25ecabe 100644 --- a/src/config.go +++ b/src/main/config.go @@ -27,7 +27,7 @@ type Config struct { type HomeAssistant struct { InstallationDate time.Time `json:"installation_date"` BaseURL string `json:"base_url"` - ApiKey string `json:"api_key"` + APIKey string `json:"api_key"` } type Sensors struct { PolledSmartEnergySummation string `json:"polled_smart_energy_summation"` @@ -54,7 +54,7 @@ func formatURL(url string) (string, error) { } url = strings.TrimSuffix(url, "/") - test := regexp.MustCompile(`(?m)https?:\/\/[^/]*`).ReplaceAllString(url, "") + test := regexp.MustCompile(`(?m)https?://[^/]*`).ReplaceAllString(url, "") if test != "" { return "", errBadHAFormat } @@ -128,9 +128,9 @@ func (config *Config) isAuthorized(c *fiber.Ctx) bool { return c.Cookies("admin_username") == config.Administrator.Username && c.Cookies("admin_password_hash") == config.Administrator.PasswordHash } -func (config *Config) equals(new *Config) bool { - return reflect.DeepEqual(new.HomeAssistant, config.HomeAssistant) && - reflect.DeepEqual(new.Sensors, config.Sensors) && - reflect.DeepEqual(new.Administrator, config.Administrator) && - reflect.DeepEqual(new.Dashboard, config.Dashboard) +func (config *Config) Equals(c *Config) bool { + return reflect.DeepEqual(c.HomeAssistant, config.HomeAssistant) && + reflect.DeepEqual(c.Sensors, config.Sensors) && + reflect.DeepEqual(c.Administrator, config.Administrator) && + reflect.DeepEqual(c.Dashboard, config.Dashboard) } diff --git a/src/database.go b/src/main/database.go similarity index 84% rename from src/database.go rename to src/main/database.go index d489bc0..8541714 100644 --- a/src/database.go +++ b/src/main/database.go @@ -1,6 +1,7 @@ package main import ( + "database/sql" "errors" "log" "time" @@ -62,18 +63,26 @@ func (config *Config) refreshCacheFromPast(pastTime time.Time) error { return err } + stmtReplace, err := config.db.Prepare("INSERT OR REPLACE INTO cache(time, green_energy_percentage, energy_consumption) values(?,?,?)") + if err != nil { + return err + } + defer stmtReplace.Close() + + stmtIgnore, err := config.db.Prepare("INSERT OR IGNORE INTO cache(time, green_energy_percentage, energy_consumption) values(?,?,?)") + if err != nil { + return err + } + defer stmtIgnore.Close() + for key, day := range greenEnergyPercentage { - var action2 string + var stmt *sql.Stmt if greenEnergyPercentage[key].Value != 0 && historyPolledSmartEnergySummation[key].Value != 0 { - action2 = "REPLACE" + stmt = stmtReplace } else { - action2 = "IGNORE" + stmt = stmtIgnore } - stmt, err := config.db.Prepare("INSERT OR " + action2 + " INTO cache(time, green_energy_percentage, energy_consumption) values(?,?,?)") - if err != nil { - return err - } _, err = stmt.Exec(day.DayTime.Unix(), greenEnergyPercentage[key].Value, historyPolledSmartEnergySummation[key].Value) if err != nil { return err @@ -101,10 +110,13 @@ func (config *Config) readHistory() (History, error) { ) err = rows.Scan(&date, &greenEnergyPercentage, &polledSmartEnergyConsumption) if err != nil { - return nil, err + return History{}, err } ret = append(ret, HistoryEntry{date, greenEnergyPercentage, polledSmartEnergyConsumption}) } + if rows.Err() != nil { + return History{}, rows.Err() + } return ret, nil } diff --git a/src/http.go b/src/main/http.go similarity index 93% rename from src/http.go rename to src/main/http.go index 4b50505..e8c7b0c 100644 --- a/src/http.go +++ b/src/main/http.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "html" "html/template" "math" "os" @@ -48,7 +49,7 @@ func (config *Config) adminEndpoint(c *fiber.Ctx) error { if err != nil { return config.renderAdminPanel(c, Warning{ Header: "An error occurred!", - Body: err.Error(), + Body: html.EscapeString(err.Error()), }) } return config.renderAdminPanel(c, Warning{ @@ -81,6 +82,7 @@ func (config *Config) renderAdminPanel(c *fiber.Ctx, warning ...Warning) error { } if len(warning) > 0 { + // #nosec // TODO this is dangerous, even if we're escaping the only place where we're passing a non-literal warning[0].BodyHTML = template.HTML(warning[0].Body) return c.Render("admin", fiber.Map{ "Defaults": config.getTemplateDefaults(), @@ -116,7 +118,7 @@ func (config *Config) saveAdminForm(c *fiber.Ctx) error { } form := &Config{ - HomeAssistant: HomeAssistant{ /*BaseURL to be filled later*/ ApiKey: c.FormValue("api_key"), InstallationDate: dayStart(parsedTime)}, + HomeAssistant: HomeAssistant{ /*BaseURL to be filled later*/ APIKey: c.FormValue("api_key"), InstallationDate: dayStart(parsedTime)}, Sensors: Sensors{PolledSmartEnergySummation: c.FormValue("polled_smart_energy_summation"), FossilPercentage: c.FormValue("fossil_percentage")}, Administrator: Administrator{Username: c.FormValue("username") /*PasswordHash to be filled later*/}, Dashboard: Dashboard{Theme: c.FormValue("theme"), Name: c.FormValue("name"), HeaderLinks: config.Dashboard.HeaderLinks, FooterLinks: config.Dashboard.FooterLinks}, @@ -134,7 +136,7 @@ func (config *Config) saveAdminForm(c *fiber.Ctx) error { } form.HomeAssistant.BaseURL = fmtURL - if form.equals(config) { + if form.Equals(config) { return errNoChanges } @@ -149,7 +151,7 @@ func (config *Config) saveAdminForm(c *fiber.Ctx) error { return err } - return os.WriteFile("config.json", js, 0o666) + return os.WriteFile("config.json", js, 0o600) } func averageExcludingCurrentDay(data []float32) float32 { @@ -207,10 +209,12 @@ func templateDivide(num1, num2 float32) template.HTML { powerOfTen := int(math.Floor(math.Log10(division))) if powerOfTen >= -2 && powerOfTen <= 2 { + // #nosec G203 // We're only printing floats return template.HTML(strconv.FormatFloat(math.Round(division*100)/100, 'f', -1, 64)) } preComma := division / math.Pow10(powerOfTen) + // #nosec G203 // We're only printing floats return template.HTML(fmt.Sprintf("%s * 10%d", strconv.FormatFloat(math.Round(preComma*100)/100, 'f', -1, 64), powerOfTen)) } @@ -218,5 +222,6 @@ func templateHTMLDateFormat(date time.Time) template.HTML { if date.IsZero() { return "" } + // #nosec G203 // We're only printing a date return template.HTML(date.Format("2006-01-02")) } diff --git a/src/main.go b/src/main/main.go similarity index 100% rename from src/main.go rename to src/main/main.go