From 1b340bc3ba4cd7515ee7e6275f3eee8e913db682 Mon Sep 17 00:00:00 2001 From: ViBiOh Date: Sun, 19 Apr 2020 12:48:09 +0200 Subject: [PATCH] feat: Adding message, refactoring getting started and renderer pkg (#16) --- README.md | 25 ++- cmd/ketchup/api.go | 8 +- go.mod | 5 +- go.sum | 22 ++- pkg/ketchup/ketchup.go | 13 +- pkg/model/model.go | 6 - pkg/renderer/handlers.go | 77 +++++++++ pkg/renderer/model.go | 7 + pkg/{ui/ui.go => renderer/renderer.go} | 47 ++---- pkg/renderer/targets.go | 133 +++++++++++++++ pkg/target/target.go | 4 +- pkg/ui/svg.go | 28 ---- pkg/ui/targets.go | 118 ------------- templates/index.html | 222 +++++++++++++++++++++++-- templates/ketchup.html | 4 + templates/style-button.html | 17 -- templates/style-color.html | 60 ------- templates/style-icon.html | 33 ---- templates/style-modal.html | 27 --- templates/style.html | 92 ---------- 20 files changed, 501 insertions(+), 447 deletions(-) create mode 100644 pkg/renderer/handlers.go create mode 100644 pkg/renderer/model.go rename pkg/{ui/ui.go => renderer/renderer.go} (55%) create mode 100644 pkg/renderer/targets.go delete mode 100644 pkg/ui/svg.go delete mode 100644 pkg/ui/targets.go delete mode 100644 templates/style-button.html delete mode 100644 templates/style-color.html delete mode 100644 templates/style-icon.html delete mode 100644 templates/style-modal.html delete mode 100644 templates/style.html diff --git a/README.md b/README.md index 9e36b924..4e8cb6db 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,15 @@ Thanks to [OpenEmoji](https://openmoji.org) for favicon. -## CI +## Getting started -Following variables are required for CI: +Golang binary is built with static link. You can download it directly from the [Github Release page](https://github.com/ViBiOh/ketchup/releases) or build it by yourself by cloning this repo and running `make`. -| Name | Purpose | -|:--:|:--:| -| **DOMAIN** | for setting Traefik domain for app | -| **DOCKER_USER** | for publishing Docker image | -| **DOCKER_PASS** | for publishing Docker image | -| **SCRIPTS_NO_INTERACTIVE** | for disabling prompt in CI | +A Docker image is available for `amd64`, `arm` and `arm64` platforms on Docker Hub: [vibioh/ketchup](https://hub.docker.com/r/vibioh/ketchup/tags). + +You can configure app by passing CLI args or environment variables (cf. [Usage](#usage) section). CLI override environment variables. + +You'll find a Kubernetes exemple (without secrets) in the [`infra/`](infra/) folder. We don't manage authentification and rely on Traefik basic-auth. ## Usage @@ -96,3 +95,13 @@ Usage of ketchup: -userAgent string [alcotest] User-Agent for check {KETCHUP_USER_AGENT} (default "Alcotest") ``` + +## CI + +Following variables are required for CI: + +| Name | Purpose | +|:--:|:--:| +| **DOCKER_USER** | for publishing Docker image | +| **DOCKER_PASS** | for publishing Docker image | +| **SCRIPTS_NO_INTERACTIVE** | for disabling prompt in CI | diff --git a/cmd/ketchup/api.go b/cmd/ketchup/api.go index 4afb7789..3b476390 100644 --- a/cmd/ketchup/api.go +++ b/cmd/ketchup/api.go @@ -18,8 +18,8 @@ import ( "github.com/ViBiOh/httputils/v3/pkg/swagger" "github.com/ViBiOh/ketchup/pkg/github" "github.com/ViBiOh/ketchup/pkg/ketchup" + "github.com/ViBiOh/ketchup/pkg/renderer" "github.com/ViBiOh/ketchup/pkg/target" - "github.com/ViBiOh/ketchup/pkg/ui" mailer "github.com/ViBiOh/mailer/pkg/client" ) @@ -63,7 +63,7 @@ func main() { targetApp := target.New(ketchupDb, githubApp) ketchupAp := ketchup.New(ketchupConfig, targetApp, githubApp, mailerApp) - uiApp, err := ui.New(targetApp) + rendererApp, err := renderer.New(targetApp) logger.Fatal(err) crudTargetApp, err := crud.New(crudTargetConfig, targetApp) @@ -74,7 +74,7 @@ func main() { swaggerHandler := http.StripPrefix(apiPath, swaggerApp.Handler()) crudTargetHandler := http.StripPrefix(targetPath, crudTargetApp.Handler()) - uiHandler := uiApp.Handler() + rendererHandler := rendererApp.Handler() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, targetPath) { @@ -90,7 +90,7 @@ func main() { http.ServeFile(w, r, path.Join("static", r.URL.Path)) } - uiHandler.ServeHTTP(w, r) + rendererHandler.ServeHTTP(w, r) }) go ketchupAp.Start() diff --git a/go.mod b/go.mod index 6f9e4c20..838e6ad8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/ViBiOh/ketchup go 1.14 require ( - github.com/ViBiOh/httputils/v3 v3.13.0 + github.com/ViBiOh/httputils/v3 v3.14.0 github.com/ViBiOh/mailer v1.6.3 + github.com/golang/protobuf v1.4.0 // indirect + github.com/prometheus/procfs v0.0.11 // indirect + golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect ) diff --git a/go.sum b/go.sum index f1aa4945..aac90b95 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/ViBiOh/httputils/v3 v3.8.3/go.mod h1:yOu5e4BzPMl7jjXOSf0nKYsHq9AJCY455yDcLlPjV7U= -github.com/ViBiOh/httputils/v3 v3.13.0 h1:AMsqtoQREdNeTTJToJyZ6SfmcZyuEN8uhD14Ym7BgtI= -github.com/ViBiOh/httputils/v3 v3.13.0/go.mod h1:exwp/UNyjMLfZTIl8i7x+AfBc4+9x2ar1VDvV0qWbv0= +github.com/ViBiOh/httputils/v3 v3.14.0 h1:0h6om6YRcugyyVxsnJnAPXlIZ4SBBW8cSrB4tWT6hGE= +github.com/ViBiOh/httputils/v3 v3.14.0/go.mod h1:exwp/UNyjMLfZTIl8i7x+AfBc4+9x2ar1VDvV0qWbv0= github.com/ViBiOh/mailer v1.6.3 h1:FPau3ISU4duB/DDvvfp93s/mXV897VNFxoyZkHy1mug= github.com/ViBiOh/mailer v1.6.3/go.mod h1:0LLlT/jx2gYWT355fUU381NFlJ0PyPuIuLoAymm8PfQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -28,6 +28,13 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -72,6 +79,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -104,11 +113,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/ketchup/ketchup.go b/pkg/ketchup/ketchup.go index 392be1a5..b09a021e 100644 --- a/pkg/ketchup/ketchup.go +++ b/pkg/ketchup/ketchup.go @@ -84,7 +84,7 @@ func (a app) checkUpdates(_ time.Time) error { newReleases = append(newReleases, release) if _, err = a.targetApp.Update(context.Background(), target); err != nil { - logger.Error("unable to update target %s: %s", target.Repository, err) + return fmt.Errorf("unable to update target %s: %s", target.Repository, err) } } else if release.TagName == target.CurrentVersion { logger.Info("%s is up-to-date!", target.Repository) @@ -96,8 +96,15 @@ func (a app) checkUpdates(_ time.Time) error { if len(newReleases) > 0 { if a.mailerApp == nil || !a.mailerApp.Enabled() { logger.Warn("mailer is not configured") - } else if err := mailer.NewEmail(a.mailerApp).Template("ketchup").From("ketchup@vibioh.fr").As("Ketchup").WithSubject("Ketchup - New update").To(a.emailTo).Data(newReleases).Send(context.Background()); err != nil { - logger.Error("unable to send email to %s: %s", a.emailTo, err) + return nil + } + + payload := map[string]interface{}{ + "targets": newReleases, + } + + if err := mailer.NewEmail(a.mailerApp).Template("ketchup").From("ketchup@vibioh.fr").As("Ketchup").WithSubject("Ketchup - New update").To(a.emailTo).Data(payload).Send(context.Background()); err != nil { + return fmt.Errorf("unable to send email to %s: %s", a.emailTo, err) } } diff --git a/pkg/model/model.go b/pkg/model/model.go index 9781817c..5b8785a3 100644 --- a/pkg/model/model.go +++ b/pkg/model/model.go @@ -4,9 +4,3 @@ package model type RowScanner interface { Scan(...interface{}) error } - -// Message for render -type Message struct { - Level string - Content string -} diff --git a/pkg/renderer/handlers.go b/pkg/renderer/handlers.go new file mode 100644 index 00000000..bd2e00b1 --- /dev/null +++ b/pkg/renderer/handlers.go @@ -0,0 +1,77 @@ +package renderer + +import ( + "fmt" + "net/http" + "strings" + + "github.com/ViBiOh/httputils/v3/pkg/httperror" + "github.com/ViBiOh/httputils/v3/pkg/logger" + "github.com/ViBiOh/httputils/v3/pkg/templates" +) + +func (a app) getData(r *http.Request) (interface{}, error) { + targets, _, err := a.targetApp.List(r.Context(), 1, 100, "", false, nil) + + return targets, err +} + +func (a app) uiHandler(w http.ResponseWriter, r *http.Request, status int, message Message) { + targets, err := a.getData(r) + if err != nil { + a.errorHandler(w, http.StatusInternalServerError, err, nil) + return + } + + content := map[string]interface{}{ + "Version": a.version, + "Targets": targets, + } + + if len(message.Content) > 0 { + content["Message"] = message + } + + if err := templates.ResponseHTMLTemplate(a.tpl.Lookup("app"), w, content, status); err != nil { + httperror.InternalServerError(w, err) + } +} + +func (a app) errorHandler(w http.ResponseWriter, status int, errs ...error) { + logger.Error("%s", errs) + + content := map[string]interface{}{ + "Version": a.version, + } + + if len(errs) > 0 { + content["Message"] = Message{ + Level: "error", + Content: errs[0].Error(), + } + + if len(errs) > 1 { + content["Errors"] = errs[1:] + } + } + + if err := templates.ResponseHTMLTemplate(a.tpl.Lookup("error"), w, content, status); err != nil { + httperror.InternalServerError(w, err) + return + } +} + +func (a app) svg() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tpl := a.tpl.Lookup(fmt.Sprintf("svg-%s", strings.Trim(r.URL.Path, "/"))) + if tpl == nil { + httperror.NotFound(w) + return + } + + w.Header().Set("Content-Type", "image/svg+xml") + if err := templates.WriteTemplate(tpl, w, r.URL.Query().Get("fill"), "text/xml"); err != nil { + httperror.InternalServerError(w, err) + } + }) +} diff --git a/pkg/renderer/model.go b/pkg/renderer/model.go new file mode 100644 index 00000000..e1719b5d --- /dev/null +++ b/pkg/renderer/model.go @@ -0,0 +1,7 @@ +package renderer + +// Message for render +type Message struct { + Level string + Content string +} diff --git a/pkg/ui/ui.go b/pkg/renderer/renderer.go similarity index 55% rename from pkg/ui/ui.go rename to pkg/renderer/renderer.go index 0c781fd1..9f6115cf 100644 --- a/pkg/ui/ui.go +++ b/pkg/renderer/renderer.go @@ -1,17 +1,15 @@ -package ui +package renderer import ( + "errors" "fmt" "html/template" "net/http" "os" "strings" - "github.com/ViBiOh/httputils/v3/pkg/crud" - "github.com/ViBiOh/httputils/v3/pkg/httperror" - "github.com/ViBiOh/httputils/v3/pkg/logger" + "github.com/ViBiOh/httputils/v3/pkg/query" "github.com/ViBiOh/httputils/v3/pkg/templates" - "github.com/ViBiOh/ketchup/pkg/model" "github.com/ViBiOh/ketchup/pkg/target" ) @@ -57,42 +55,19 @@ func (a app) Handler() http.Handler { return } - if strings.HasPrefix(r.URL.Path, targetsPath) { - targetsHandler.ServeHTTP(w, r) + if query.IsRoot(r) { + a.uiHandler(w, r, http.StatusOK, Message{ + Level: r.URL.Query().Get("messageLevel"), + Content: r.URL.Query().Get("messageContent"), + }) return } - targets, _, err := a.targetApp.List(r.Context(), 1, 100, "", false, nil) - if err != nil { - a.handleError(w, http.StatusInternalServerError, err, nil) + if strings.HasPrefix(r.URL.Path, targetsPath) { + targetsHandler.ServeHTTP(w, r) return } - content := map[string]interface{}{ - "Version": a.version, - "Targets": targets, - } - - if err := templates.ResponseHTMLTemplate(a.tpl.Lookup("app"), w, content, http.StatusOK); err != nil { - httperror.InternalServerError(w, err) - } + a.errorHandler(w, http.StatusNotFound, errors.New("page not found"), nil) }) } - -func (a app) handleError(w http.ResponseWriter, status int, err error, errors []crud.Error) { - logger.Error("%s", err) - - content := map[string]interface{}{ - "Version": a.version, - "Message": model.Message{ - Level: "error", - Content: err.Error(), - }, - "Errors": errors, - } - - if err := templates.ResponseHTMLTemplate(a.tpl.Lookup("error"), w, content, status); err != nil { - httperror.InternalServerError(w, err) - return - } -} diff --git a/pkg/renderer/targets.go b/pkg/renderer/targets.go new file mode 100644 index 00000000..7a8884df --- /dev/null +++ b/pkg/renderer/targets.go @@ -0,0 +1,133 @@ +package renderer + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/ViBiOh/httputils/v3/pkg/crud" + "github.com/ViBiOh/ketchup/pkg/target" +) + +// SVG render a svg in given coolor +func (a app) targets() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + a.errorHandler(w, http.StatusMethodNotAllowed, fmt.Errorf("invalid method %s", r.Method), nil) + return + } + + if err := r.ParseForm(); err != nil { + a.errorHandler(w, http.StatusBadRequest, err, nil) + return + } + + method := strings.ToUpper(r.FormValue("method")) + + switch method { + case http.MethodPost: + a.handleCreate(w, r) + case http.MethodPut: + a.handleUpdate(w, r) + case http.MethodDelete: + a.handleDelete(w, r) + default: + a.errorHandler(w, http.StatusBadRequest, fmt.Errorf("invalid method %s", method), nil) + } + }) +} + +func (a app) handleCreate(w http.ResponseWriter, r *http.Request) { + target := target.Target{ + Repository: r.FormValue("repository"), + CurrentVersion: r.FormValue("currentVersion"), + } + + if errs := a.targetApp.Check(r.Context(), nil, target); len(errs) > 0 { + a.handleCrudError(w, errs) + return + } + + if _, err := a.targetApp.Create(r.Context(), target); err != nil { + a.errorHandler(w, http.StatusInternalServerError, err, nil) + return + } + + http.Redirect(w, r, fmt.Sprintf("/?messageContent=%s", url.QueryEscape(fmt.Sprintf("%s created with success!", target.Repository))), http.StatusFound) +} + +func (a app) handleUpdate(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseUint(strings.Trim(r.URL.Path, "/"), 10, 64) + if err != nil { + a.errorHandler(w, http.StatusBadRequest, err, nil) + return + } + + rawOldTarget, err := a.targetApp.Get(r.Context(), id) + if err != nil { + a.errorHandler(w, http.StatusBadRequest, err, nil) + return + } + + oldTarget := rawOldTarget.(target.Target) + + newTarget := target.Target{ + ID: id, + Repository: r.FormValue("repository"), + CurrentVersion: r.FormValue("currentVersion"), + LatestVersion: oldTarget.LatestVersion, + } + + if errs := a.targetApp.Check(r.Context(), oldTarget, newTarget); len(errs) > 0 { + a.handleCrudError(w, errs) + return + } + + if _, err := a.targetApp.Update(r.Context(), newTarget); err != nil { + a.errorHandler(w, http.StatusInternalServerError, err, nil) + return + } + + http.Redirect(w, r, fmt.Sprintf("/?messageContent=%s", url.QueryEscape(fmt.Sprintf("%s updated with success!", newTarget.Repository))), http.StatusFound) +} + +func (a app) handleDelete(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseUint(strings.Trim(r.URL.Path, "/"), 10, 64) + if err != nil { + a.errorHandler(w, http.StatusBadRequest, err, nil) + return + } + + rawTarget, err := a.targetApp.Get(r.Context(), id) + if err != nil { + a.errorHandler(w, http.StatusBadRequest, err, nil) + return + } + + target := rawTarget.(target.Target) + + if errs := a.targetApp.Check(r.Context(), target, nil); len(errs) > 0 { + a.handleCrudError(w, errs) + return + } + + if err := a.targetApp.Delete(r.Context(), target); err != nil { + a.errorHandler(w, http.StatusInternalServerError, err, nil) + return + } + + http.Redirect(w, r, fmt.Sprintf("/?messageContent=%s", url.QueryEscape(fmt.Sprintf("%s deleted with success!", target.Repository))), http.StatusFound) +} + +func (a app) handleCrudError(w http.ResponseWriter, errs []crud.Error) { + errorsValues := make([]error, 1+len(errs)) + errorsValues[0] = errors.New("invalid form") + for i, err := range errs { + errorsValues[i+1] = err + } + + a.errorHandler(w, http.StatusBadRequest, errorsValues...) +} diff --git a/pkg/target/target.go b/pkg/target/target.go index 5b639281..17224df1 100644 --- a/pkg/target/target.go +++ b/pkg/target/target.go @@ -16,7 +16,7 @@ var _ crud.Service = &app{} // App of package type App interface { - Unmarshal(data []byte) (interface{}, error) + Unmarshal(data []byte, contentType string) (interface{}, error) Check(ctx context.Context, old, new interface{}) []crud.Error List(ctx context.Context, page, pageSize uint, sortKey string, sortDesc bool, filters map[string][]string) ([]interface{}, uint, error) Get(ctx context.Context, ID uint64) (interface{}, error) @@ -39,7 +39,7 @@ func New(db *sql.DB, githubApp github.App) App { } // Unmarshal a Target -func (a app) Unmarshal(content []byte) (interface{}, error) { +func (a app) Unmarshal(content []byte, contentType string) (interface{}, error) { var o Target if err := json.Unmarshal(content, &o); err != nil { diff --git a/pkg/ui/svg.go b/pkg/ui/svg.go deleted file mode 100644 index 9c8bb418..00000000 --- a/pkg/ui/svg.go +++ /dev/null @@ -1,28 +0,0 @@ -package ui - -import ( - "fmt" - "net/http" - "strings" - - "github.com/ViBiOh/httputils/v3/pkg/httperror" - "github.com/ViBiOh/httputils/v3/pkg/templates" -) - -// SVG render a svg in given coolor -func (a app) svg() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tpl := a.tpl.Lookup(fmt.Sprintf("svg-%s", strings.Trim(r.URL.Path, "/"))) - if tpl == nil { - httperror.NotFound(w) - return - } - - w.Header().Set("Content-Type", "image/svg+xml") - - if err := templates.WriteTemplate(tpl, w, r.URL.Query().Get("fill"), "text/xml"); err != nil { - httperror.InternalServerError(w, err) - return - } - }) -} diff --git a/pkg/ui/targets.go b/pkg/ui/targets.go deleted file mode 100644 index 441653cf..00000000 --- a/pkg/ui/targets.go +++ /dev/null @@ -1,118 +0,0 @@ -package ui - -import ( - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/ViBiOh/ketchup/pkg/target" -) - -// SVG render a svg in given coolor -func (a app) targets() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - a.handleError(w, http.StatusMethodNotAllowed, fmt.Errorf("invalid method %s", r.Method), nil) - return - } - - if err := r.ParseForm(); err != nil { - a.handleError(w, http.StatusBadRequest, err, nil) - return - } - - method := strings.ToUpper(r.FormValue("method")) - - switch method { - case http.MethodPost: - a.handleCreate(w, r) - case http.MethodPut: - a.handleUpdate(w, r) - case http.MethodDelete: - a.handleDelete(w, r) - default: - a.handleError(w, http.StatusBadRequest, fmt.Errorf("invalid method %s", method), nil) - } - }) -} - -func (a app) handleCreate(w http.ResponseWriter, r *http.Request) { - target := target.Target{ - Repository: r.FormValue("repository"), - CurrentVersion: r.FormValue("currentVersion"), - } - - if errors := a.targetApp.Check(r.Context(), nil, target); len(errors) > 0 { - a.handleError(w, http.StatusBadRequest, fmt.Errorf("invalid form"), errors) - return - } - - if _, err := a.targetApp.Create(r.Context(), target); err != nil { - a.handleError(w, http.StatusInternalServerError, err, nil) - return - } - - http.Redirect(w, r, "/", http.StatusFound) -} - -func (a app) handleUpdate(w http.ResponseWriter, r *http.Request) { - id, err := strconv.ParseUint(strings.Trim(r.URL.Path, "/"), 10, 64) - if err != nil { - a.handleError(w, http.StatusBadRequest, err, nil) - return - } - - rawOldTarget, err := a.targetApp.Get(r.Context(), id) - if err != nil { - a.handleError(w, http.StatusBadRequest, err, nil) - return - } - - oldTarget := rawOldTarget.(target.Target) - - newTarget := target.Target{ - ID: id, - Repository: r.FormValue("repository"), - CurrentVersion: r.FormValue("currentVersion"), - LatestVersion: oldTarget.LatestVersion, - } - - if errors := a.targetApp.Check(r.Context(), oldTarget, newTarget); len(errors) > 0 { - a.handleError(w, http.StatusBadRequest, fmt.Errorf("invalid form"), errors) - return - } - - if _, err := a.targetApp.Update(r.Context(), newTarget); err != nil { - a.handleError(w, http.StatusInternalServerError, err, nil) - return - } - - http.Redirect(w, r, "/", http.StatusFound) -} - -func (a app) handleDelete(w http.ResponseWriter, r *http.Request) { - id, err := strconv.ParseUint(strings.Trim(r.URL.Path, "/"), 10, 64) - if err != nil { - a.handleError(w, http.StatusBadRequest, err, nil) - return - } - - target, err := a.targetApp.Get(r.Context(), id) - if err != nil { - a.handleError(w, http.StatusBadRequest, err, nil) - return - } - - if errors := a.targetApp.Check(r.Context(), target, nil); len(errors) > 0 { - a.handleError(w, http.StatusBadRequest, fmt.Errorf("invalid form"), errors) - return - } - - if err := a.targetApp.Delete(r.Context(), target); err != nil { - a.handleError(w, http.StatusInternalServerError, err, nil) - return - } - - http.Redirect(w, r, "/", http.StatusFound) -} diff --git a/templates/index.html b/templates/index.html index ca650b0d..4cbe23bd 100644 --- a/templates/index.html +++ b/templates/index.html @@ -12,11 +12,211 @@ - {{ template "style-color" . }} - {{ template "style-icon" . }} - {{ template "style-button" . }} - {{ template "style-modal" . }} - {{ template "style" . }} + @@ -39,16 +239,18 @@

{{ define "message" }} {{ if . }} -

- {{ .Content }} -

+ {{ if gt (len .Content) 0 }} +

+ {{ .Content }} +

+ {{ end }} {{ end }} {{ end }} {{ define "app" }} {{ template "header" . }} - {{ template "targets" .Targets }} + {{- template "targets" .Targets -}} {{ template "footer" . }} {{ end }} @@ -59,7 +261,7 @@

{{ if .Errors }} {{ range .Errors }}

- {{ .Label }} + {{ . }}

{{ end }} {{ end }} diff --git a/templates/ketchup.html b/templates/ketchup.html index 0a529b83..fe467430 100644 --- a/templates/ketchup.html +++ b/templates/ketchup.html @@ -104,6 +104,10 @@

Confirmation

display: inline-block; } + .block { + display: block; + } + @media screen and (max-width: 640px) { .target { width: auto; diff --git a/templates/style-button.html b/templates/style-button.html deleted file mode 100644 index e4b4cb8e..00000000 --- a/templates/style-button.html +++ /dev/null @@ -1,17 +0,0 @@ -{{ define "style-button" }} - -{{ end }} diff --git a/templates/style-color.html b/templates/style-color.html deleted file mode 100644 index 03961a7b..00000000 --- a/templates/style-color.html +++ /dev/null @@ -1,60 +0,0 @@ -{{ define "style-color" }} - -{{ end }} diff --git a/templates/style-icon.html b/templates/style-icon.html deleted file mode 100644 index 024ff154..00000000 --- a/templates/style-icon.html +++ /dev/null @@ -1,33 +0,0 @@ -{{ define "style-icon" }} - -{{ end }} diff --git a/templates/style-modal.html b/templates/style-modal.html deleted file mode 100644 index bf4b4427..00000000 --- a/templates/style-modal.html +++ /dev/null @@ -1,27 +0,0 @@ -{{ define "style-modal" }} - {{ $root := . }} - - -{{ end }} diff --git a/templates/style.html b/templates/style.html deleted file mode 100644 index e0c1e624..00000000 --- a/templates/style.html +++ /dev/null @@ -1,92 +0,0 @@ -{{ define "style" }} - -{{ end }}