diff options
author | Julian T <julian@jtle.dk> | 2020-09-09 15:44:07 +0200 |
---|---|---|
committer | Julian T <julian@jtle.dk> | 2020-09-09 15:44:07 +0200 |
commit | d9cc1aabe83261e5249f7493a59c484fb99b4772 (patch) | |
tree | 1a351c53c0adfeb4dacdb2ee933c359965860108 |
Initial working version
-rw-r--r-- | config.json | 5 | ||||
-rw-r--r-- | main.go | 169 | ||||
-rw-r--r-- | root.template | 57 |
3 files changed, 231 insertions, 0 deletions
diff --git a/config.json b/config.json new file mode 100644 index 0000000..ec59916 --- /dev/null +++ b/config.json @@ -0,0 +1,5 @@ +{ + "listen": "localhost:8080", + "db": "user=julian dbname=noteman sslmode=disable", + "tmpl": "" +} @@ -0,0 +1,169 @@ +package main + +import ( + "net/http" + "flag" + "fmt" + "strings" + "crypto/md5" + "encoding/json" + "os" + "html/template" + "log" + "path" + + _"github.com/lib/pq" + "github.com/jmoiron/sqlx" +) + +type Config struct { + Listen string `json:"listen"` + DBStr string `json:"db"` + TmplPath string `json:"tmpl"` +} + +type Server struct { + mux *http.ServeMux + conf *Config + db *sqlx.DB +} + +type note struct { + Hash string + Name string + Location string +} + +const scheme = ` + CREATE TABLE IF NOT EXISTS notes ( + hash VARCHAR(4) PRIMARY KEY, + name TEXT NOT NULL, + location TEXT NOT NULL, + available BOOL DEFAULT True + ) +` + +func main() { + confFlag := flag.String("c", "config.json", "config file") + flag.Parse() + + f, err := os.Open(*confFlag) + if err != nil { + log.Fatal(err) + } + + var conf Config + err = json.NewDecoder(f).Decode(&conf) + if err != nil { + log.Fatal(err) + } + + s := NewServer(&conf) + if err != nil { + log.Fatal(err) + } + + err = s.Connect() + if err != nil { + log.Fatal(err) + } + + log.Fatal(s.Run()) +} + +func NewServer(conf *Config) *Server { + s := &Server{} + s.mux = http.NewServeMux() + s.mux.HandleFunc("/", s.httpRoot) + s.conf = conf + + return s +} + +func (s *Server) Connect() error { + var err error + s.db, err = sqlx.Connect("postgres", s.conf.DBStr) + if err != nil { + return err + } + + s.db.MustExec(scheme) + return nil +} + +func (s *Server) Run() error { + log.Printf("Listening on %s", s.conf.Listen) + return http.ListenAndServe(s.conf.Listen, s.mux) +} + +func (s *Server) renderTemplate(w http.ResponseWriter, data interface{}, pathname string) error { + + tmpl, err := template.ParseFiles(path.Join(s.conf.TmplPath, pathname)) + if err != nil { + log.Print(err) + return err + } + + err = tmpl.Execute(w, data) + if err != nil { + log.Print(err) + } + return err +} + +func (s *Server) httpCreateNode(w http.ResponseWriter, r *http.Request) { + var ( + name = r.FormValue("name") + location = r.FormValue("location") + ) + + hash := fmt.Sprintf("%x", md5.Sum([]byte(name)))[0:4] + + _, err := s.db.ExecContext(r.Context(), ` + INSERT INTO notes (hash, name, location) + VALUES ($1, $2, $3)`, hash, name, location) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var ref strings.Builder + fmt.Fprintf(&ref, "#%s", hash) + if location != "" { + fmt.Fprintf(&ref, "/%s", location) + } + + fmt.Fprintf(w, ` + <html> + <p><b>Note:</b> %s</p> + <p><b>Location:</b> %s</p> + <p><b>Hash:</b> %s"</p> + <p><b>Ref:</b> %s</p> + </html>`, name, location, hash, ref.String()) +} + +func (s *Server) httpRoot(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + return + } + + if r.Method == "POST" { + s.httpCreateNode(w, r) + return + } + + var page struct { + Notes []note + Msg string + } + + err := s.db.SelectContext(r.Context(), &page.Notes, ` + SELECT hash, name, location FROM notes WHERE available = True`) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + s.renderTemplate(w, &page, "root.template") +} diff --git a/root.template b/root.template new file mode 100644 index 0000000..a492b7a --- /dev/null +++ b/root.template @@ -0,0 +1,57 @@ +<html> + <head> + <title>Noteman</title> + <style> + .box { + padding: 10px; + border: 1px solid black; + } + #name { + width: 300px; + } + td { + padding: 0 15px + } + </style> + </head> + <body> + <div class="box"> + <table> + <tr> + <th>Location format</th> + </tr> + <tr> + <td>Page</td> + <td>P</td> + </tr> + <tr> + <td>Book</td> + <td>B[id]S[page]</td> + </tr> + <tr> + <td>Notes git repo</td> + <td>G</td> + </tr> + </table><br><br> + <form method="POST"> + <label for="name">Name</label> + <input id="name" name="name" type="text"> + <label for="location">Location</label> + <input id="location" name="location" type="text"> + <input type="submit" value="go"> + </form> + </div><br><br> + <table> + <tr> + <th>Ref</th> + <th>Name</th> + </tr> + {{ range .Notes }} + <tr> + <td>#{{ .Hash }}/{{ .Location }}</td> + <td>{{ .Name }}</td> + </tr> + {{ end }} + </table> + </body> +</html> |