From e6f8939ee78c293c893f70f1db2b2a00fea2e914 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 11 Feb 2023 21:05:35 +0100 Subject: [PATCH] Add products api (#6205) --- server/http.go | 1 + server/http_config_handler.go | 90 +++++++++++++++++++ util/templates/init.go | 11 +++ .../templates/{template_types.go => types.go} | 9 ++ 4 files changed, 111 insertions(+) rename util/templates/{template_types.go => types.go} (97%) diff --git a/server/http.go b/server/http.go index fc631d8b3..ee7100a59 100644 --- a/server/http.go +++ b/server/http.go @@ -98,6 +98,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) { "health": {[]string{"GET"}, "/health", healthHandler(site)}, "state": {[]string{"GET"}, "/state", stateHandler(cache)}, "config": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", templatesHandler}, + "products": {[]string{"GET"}, "/config/products/{class:[a-z]+}", productsHandler}, "test": {[]string{"POST"}, "/config/test/{class:[a-z]+}", testHandler}, "buffersoc": {[]string{"POST", "OPTIONS"}, "/buffersoc/{value:[0-9.]+}", floatHandler(site.SetBufferSoc, site.GetBufferSoc)}, "prioritysoc": {[]string{"POST", "OPTIONS"}, "/prioritysoc/{value:[0-9.]+}", floatHandler(site.SetPrioritySoc, site.GetPrioritySoc)}, diff --git a/server/http_config_handler.go b/server/http_config_handler.go index 898fd961a..2a3e68d6c 100644 --- a/server/http_config_handler.go +++ b/server/http_config_handler.go @@ -2,7 +2,9 @@ package server import ( "encoding/json" + "errors" "net/http" + "strings" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/charger" @@ -10,6 +12,7 @@ import ( "github.com/evcc-io/evcc/util/templates" "github.com/evcc-io/evcc/vehicle" "github.com/gorilla/mux" + "golang.org/x/exp/slices" ) // templatesHandler returns the list of templates by class @@ -23,6 +26,93 @@ func templatesHandler(w http.ResponseWriter, r *http.Request) { } res := templates.ByClass(class) + + lang := r.URL.Query().Get("lang") + templates.EncoderLanguage(lang) + + if name := r.URL.Query().Get("name"); name != "" { + for _, t := range res { + if t.TemplateDefinition.Template == name { + jsonResult(w, t) + return + } + } + + jsonError(w, http.StatusBadRequest, errors.New("template not found")) + return + } + + jsonResult(w, res) +} + +type product struct { + Name string `json:"name"` + Template string `json:"template"` +} + +type products []product + +func (p products) MarshalJSON() (out []byte, err error) { + if p == nil { + return []byte(`null`), nil + } + if len(p) == 0 { + return []byte(`{}`), nil + } + + out = append(out, '{') + for _, e := range p { + key, err := json.Marshal(e.Name) + if err != nil { + return nil, err + } + val, err := json.Marshal(e.Template) + if err != nil { + return nil, err + } + out = append(out, key...) + out = append(out, ':') + out = append(out, val...) + out = append(out, ',') + } + + // replace last ',' with '}' + if len(out) > 1 { + out[len(out)-1] = '}' + } else { + out = append(out, '}') + } + + return out, nil +} + +// productsHandler returns the list of products by class +func productsHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + class, err := templates.ClassString(vars["class"]) + if err != nil { + jsonError(w, http.StatusBadRequest, err) + return + } + + tmpl := templates.ByClass(class) + lang := r.URL.Query().Get("lang") + + res := make(products, 0) + for _, t := range tmpl { + for _, p := range t.Products { + res = append(res, product{ + Name: p.Title(lang), + Template: t.TemplateDefinition.Template, + }) + } + } + + slices.SortFunc(res, func(a, b product) bool { + return strings.ToLower(a.Name) < strings.ToLower(b.Name) + }) + jsonResult(w, res) } diff --git a/util/templates/init.go b/util/templates/init.go index 8a0fac2d5..544a55b35 100644 --- a/util/templates/init.go +++ b/util/templates/init.go @@ -5,6 +5,7 @@ import ( "fmt" "io/fs" "path" + "sync" "github.com/evcc-io/evcc/templates/definition" "golang.org/x/exp/slices" @@ -14,6 +15,9 @@ import ( var ( templates = make(map[Class][]Template) configDefaults = ConfigDefaults{} + + mu sync.Mutex + encoderLanguage string ) func init() { @@ -98,6 +102,13 @@ func loadTemplates(class Class) { } } +// EncoderLanguage sets the template language for encoding json +func EncoderLanguage(lang string) { + mu.Lock() + defer mu.Unlock() + encoderLanguage = lang +} + func ByClass(class Class) []Template { return templates[class] } diff --git a/util/templates/template_types.go b/util/templates/types.go similarity index 97% rename from util/templates/template_types.go rename to util/templates/types.go index 9df7b426c..330e63c40 100644 --- a/util/templates/template_types.go +++ b/util/templates/types.go @@ -2,6 +2,7 @@ package templates import ( "bufio" + "encoding/json" "fmt" "strings" @@ -145,6 +146,14 @@ func (t *TextLanguage) Update(new TextLanguage, always bool) { } } +// MarshalJSON implements the json.Marshaler interface +func (t *TextLanguage) MarshalJSON() (out []byte, err error) { + mu.Lock() + s := t.String(encoderLanguage) + mu.Unlock() + return json.Marshal(s) +} + // Requirements type Requirements struct { EVCC []string // EVCC requirements, e.g. sponsorship