summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxengineering <me@xengineering.eu>2023-12-27 13:33:59 +0100
committerxengineering <me@xengineering.eu>2023-12-27 13:33:59 +0100
commitc6d2e9dadcd08cdf82aab507d9d4d8d58574df20 (patch)
tree65495b3fd84060638c5dc396417b6aab926bca79
parent1978c5257e2cf4720716b59c3d69dc21cf457c18 (diff)
downloadceres-c6d2e9dadcd08cdf82aab507d9d4d8d58574df20.tar
ceres-c6d2e9dadcd08cdf82aab507d9d4d8d58574df20.tar.zst
ceres-c6d2e9dadcd08cdf82aab507d9d4d8d58574df20.zip
Implement index page with recipe overview
-rw-r--r--main.go5
-rw-r--r--model/data.go5
-rw-r--r--model/index.go29
-rw-r--r--model/sql/index-select.sql1
-rw-r--r--view/common.go64
-rw-r--r--view/html/footer.html5
-rw-r--r--view/html/head.html8
-rw-r--r--view/html/index.html40
8 files changed, 157 insertions, 0 deletions
diff --git a/main.go b/main.go
index 44e4172..0d84eef 100644
--- a/main.go
+++ b/main.go
@@ -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}}