diff options
author | xengineering <me@xengineering.eu> | 2023-12-27 13:33:59 +0100 |
---|---|---|
committer | xengineering <me@xengineering.eu> | 2023-12-27 13:33:59 +0100 |
commit | c6d2e9dadcd08cdf82aab507d9d4d8d58574df20 (patch) | |
tree | 65495b3fd84060638c5dc396417b6aab926bca79 | |
parent | 1978c5257e2cf4720716b59c3d69dc21cf457c18 (diff) | |
download | ceres-c6d2e9dadcd08cdf82aab507d9d4d8d58574df20.tar ceres-c6d2e9dadcd08cdf82aab507d9d4d8d58574df20.tar.zst ceres-c6d2e9dadcd08cdf82aab507d9d4d8d58574df20.zip |
Implement index page with recipe overview
-rw-r--r-- | main.go | 5 | ||||
-rw-r--r-- | model/data.go | 5 | ||||
-rw-r--r-- | model/index.go | 29 | ||||
-rw-r--r-- | model/sql/index-select.sql | 1 | ||||
-rw-r--r-- | view/common.go | 64 | ||||
-rw-r--r-- | view/html/footer.html | 5 | ||||
-rw-r--r-- | view/html/head.html | 8 | ||||
-rw-r--r-- | view/html/index.html | 40 |
8 files changed, 157 insertions, 0 deletions
@@ -10,6 +10,7 @@ import ( "syscall" "xengineering.eu/ceres/model" + "xengineering.eu/ceres/view" "github.com/gorilla/mux" ) @@ -21,6 +22,8 @@ func main() { model.InitDatabase() defer model.CloseDatabase() + view.Init() + var srv *http.Server = startServer("127.0.0.1:8080") go srv.ListenAndServe() defer stopServer(srv) @@ -41,6 +44,8 @@ func startServer(addr string) *http.Server { r.PathPrefix("/static/"). Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static)))) + r.HandleFunc("/", view.HandlerHTML(&model.Index{})).Methods(`GET`) + muxer := http.NewServeMux() muxer.Handle("/", r) diff --git a/model/data.go b/model/data.go new file mode 100644 index 0000000..b5edd85 --- /dev/null +++ b/model/data.go @@ -0,0 +1,5 @@ +package model + +type ReadableData interface { + FromDB() error +} diff --git a/model/index.go b/model/index.go new file mode 100644 index 0000000..cc7ffd5 --- /dev/null +++ b/model/index.go @@ -0,0 +1,29 @@ +package model + +type IndexElement struct { + Id uint + Title string +} + +type Index []IndexElement + +func (d *Index) FromDB() error { + query, err := GetSql(`index-select`) + if err != nil { + return err + } + rows, err := db.Query(query) + if err != nil { + return err + } + + for rows.Next() { + var i IndexElement + if err = rows.Scan(&i.Id, &i.Title); err != nil { + return err + } + *d = append(*d, i) + } + + return nil +} diff --git a/model/sql/index-select.sql b/model/sql/index-select.sql new file mode 100644 index 0000000..20b6126 --- /dev/null +++ b/model/sql/index-select.sql @@ -0,0 +1 @@ +SELECT id,title FROM recipes; diff --git a/view/common.go b/view/common.go new file mode 100644 index 0000000..c371fa0 --- /dev/null +++ b/view/common.go @@ -0,0 +1,64 @@ +package view + +import ( + "embed" + "html/template" + "net/http" + "strings" + "reflect" + + "xengineering.eu/ceres/model" + + "github.com/gorilla/mux" +) + +//go:embed html/*.html +var htmlFS embed.FS + +var html *template.Template + +func Init() { + html = template.Must(template.New("html").ParseFS(htmlFS, "html/*.html")) +} + +func HandlerHTML(prototype model.ReadableData) http.HandlerFunc { + t := reflect.TypeOf(prototype).Elem() + + tmpl := t.String() + tmpl = strings.TrimPrefix(tmpl, `model.`) + tmpl = strings.ToLower(tmpl) + + return func(w http.ResponseWriter, r *http.Request) { + data := reflect.New(t).Interface().(model.ReadableData) + var err error + + v := reflect.ValueOf(data).Elem() + if v.Kind() == reflect.Struct { + id, ok := mux.Vars(r)[`id`] + if ok { + f := v.FieldByName(`Id`) + if f.IsValid() && f.CanSet() && f.Kind() == reflect.String { + f.SetString(id) + } else { + http.Error(w, `Requested struct data does not have a settable string ID`, http.StatusBadRequest) + return + } + } else { + http.Error(w, `Requested struct data without giving an ID`, http.StatusBadRequest) + return + } + } + + err = data.FromDB() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = html.ExecuteTemplate(w, tmpl, data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} diff --git a/view/html/footer.html b/view/html/footer.html new file mode 100644 index 0000000..5128b7f --- /dev/null +++ b/view/html/footer.html @@ -0,0 +1,5 @@ +{{define "footer"}} +<footer> + <p>The <a href="https://xengineering.eu/git/ceres">Ceres</a> recipe server is licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL v3</a> and developed with <a href="https://simplecss.org/">simple.css</a>.</p> +</footer> +{{end}} diff --git a/view/html/head.html b/view/html/head.html new file mode 100644 index 0000000..91ae004 --- /dev/null +++ b/view/html/head.html @@ -0,0 +1,8 @@ +{{define "head"}} +<head> + <title>Recipes</title> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" href="/static/view/static/simple.css/simple.css" type="text/css"> +</head> +{{end}} diff --git a/view/html/index.html b/view/html/index.html new file mode 100644 index 0000000..45f600f --- /dev/null +++ b/view/html/index.html @@ -0,0 +1,40 @@ +{{define "index"}} +<html> + {{ template "head" }} + <header> + <nav> + <a href="/">HOME</a> + </nav> + <h1>Recipe overview</h1> + </header> + <body> + <main> + <p>Here are the available recipes 😋🍳🍔🍕🥘</p> + <input id="search" onkeyup="filter()" type="text" placeholder="Search for a recipe ..."></input> + <ul id="recipes">{{range .}} + <li><a href="./recipes/{{.Id}}">{{.Title}}</a></li>{{end}} + </ul> + </main> + {{ template "footer" }} + <script> + function filter() { + var input, query, ul, li, a, i, txtValue; + input = document.getElementById('search'); + query = input.value.toUpperCase(); + ul = document.getElementById("recipes"); + li = ul.getElementsByTagName('li'); + + for (i = 0; i < li.length; i++) { + a = li[i].getElementsByTagName("a")[0]; + txtValue = a.textContent || a.innerText; + if (txtValue.toUpperCase().indexOf(query) > -1) { + li[i].style.display = ""; + } else { + li[i].style.display = "none"; + } + } + } + </script> + </body> +</html> +{{end}} |