From f022b2698744cbba07c612509ba43d34f738906a Mon Sep 17 00:00:00 2001 From: bel Date: Thu, 12 Mar 2020 13:48:04 -0600 Subject: [PATCH] Base project to template --- .gitignore | 10 ++++++ config/config.go | 53 ++++++++++++++++++++++++++++ config/config_test.go | 57 ++++++++++++++++++++++++++++++ main.go | 68 ++++++++++++++++++++++++++++++++++++ server/.notes/create.go | 24 +++++++++++++ server/.notes/create_test.go | 1 + server/.notes/dir.go | 37 ++++++++++++++++++++ server/.notes/dir_test.go | 26 ++++++++++++++ server/.notes/edit.go | 43 +++++++++++++++++++++++ server/.notes/edit_test.go | 1 + server/.notes/file.go | 29 +++++++++++++++ server/.notes/file_test.go | 46 ++++++++++++++++++++++++ server/.notes/notes.go | 13 +++++++ server/.notes/rnotes.go | 27 ++++++++++++++ server/.notes/rnotes_test.go | 1 + server/.notes/submit.go | 31 ++++++++++++++++ server/.notes/submit_test.go | 1 + server/routes.go | 43 +++++++++++++++++++++++ server/server.go | 32 +++++++++++++++++ 19 files changed, 543 insertions(+) create mode 100755 .gitignore create mode 100755 config/config.go create mode 100644 config/config_test.go create mode 100755 main.go create mode 100755 server/.notes/create.go create mode 100755 server/.notes/create_test.go create mode 100755 server/.notes/dir.go create mode 100755 server/.notes/dir_test.go create mode 100755 server/.notes/edit.go create mode 100755 server/.notes/edit_test.go create mode 100755 server/.notes/file.go create mode 100755 server/.notes/file_test.go create mode 100755 server/.notes/notes.go create mode 100755 server/.notes/rnotes.go create mode 100755 server/.notes/rnotes_test.go create mode 100755 server/.notes/submit.go create mode 100755 server/.notes/submit_test.go create mode 100755 server/routes.go create mode 100755 server/server.go diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..2c17f32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +gollum +public +**.sw* +**/**.sw* +*.sw* +**/*.sw* +notes-server +exec-notes-server +firestormy +exec-firestormy diff --git a/config/config.go b/config/config.go new file mode 100755 index 0000000..d3062ad --- /dev/null +++ b/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "fmt" + "local/args" + "local/storage" + "os" + "strings" +) + +var ( + Port string + OAuthServer string + Store storage.DB + StoreType string + StoreAddr string + StoreUser string + StorePass string +) + +func init() { + Refresh() +} + +func Refresh() { + if strings.Contains(fmt.Sprint(os.Args), " -test") { + return + } + + as := args.NewArgSet() + as.Append(args.STRING, "port", "port to listen on", "49809") + as.Append(args.STRING, "oauth", "oauth URL", "") + as.Append(args.STRING, "storetype", "storage type", "map") + as.Append(args.STRING, "storeaddr", "storage address", "") + as.Append(args.STRING, "storeuser", "storage username", "") + as.Append(args.STRING, "storepass", "storage password", "") + if err := as.Parse(); err != nil { + panic(err) + } + + Port = ":" + strings.TrimPrefix(as.Get("port").GetString(), ":") + OAuthServer = as.Get("oauth").GetString() + StoreType = as.Get("storetype").GetString() + StoreAddr = as.Get("storeaddr").GetString() + StoreUser = as.Get("storeuser").GetString() + StorePass = as.Get("storepass").GetString() + + if db, err := storage.New(storage.TypeFromString(StoreType), StoreAddr, StoreUser, StorePass); err != nil { + panic(err) + } else { + Store = db + } +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..1a51924 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,57 @@ +package config + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestConfigRefresh(t *testing.T) { + was := os.Args + defer func() { + os.Args = was + }() + os.Args = []string{"na"} + + d, err := ioutil.TempDir(os.TempDir(), "firestormy.config.test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + os.Setenv("PORT", "11111") + os.Setenv("OAUTH", "localhost:27777") + os.Setenv("STORETYPE", "leveldb") + os.Setenv("STOREADDR", d) + os.Setenv("STOREUSER", "user") + os.Setenv("STOREPASS", "pass") + + Refresh() + + if v := Port; v != ":11111" { + t.Error(v) + } + if v := OAuthServer; v != "localhost:27777" { + t.Error(v) + } + if v := StoreType; v != "leveldb" { + t.Error(v) + } + if v := StoreAddr; v != d { + t.Error(v) + } + if v := StoreUser; v != "user" { + t.Error(v) + } + if v := StorePass; v != "pass" { + t.Error(v) + } + + if err := Store.Set("key", []byte("value")); err != nil { + t.Error(err) + } else if b, err := Store.Get("key"); err != nil { + t.Error(err) + } else if string(b) != "value" { + t.Error(err) + } +} diff --git a/main.go b/main.go new file mode 100755 index 0000000..5cd3a64 --- /dev/null +++ b/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "local/firestormy/config" + "local/firestormy/server" + "local/lastn/lastn" + "log" + "net/http" + "os" + "os/signal" + "path/filepath" + "time" +) + +func main() { + server := server.New() + if err := server.Routes(); err != nil { + panic(err) + } + + go func() { + log.Printf("Serving on %q", config.Port) + if err := http.ListenAndServe(config.Port, server); err != nil { + panic(err) + } + }() + + // catch stop + stop := make(chan os.Signal) + signal.Notify(stop, os.Interrupt) + <-stop +} + +func EnqueueBackups() { + realpath, err := filepath.Abs(config.StoreAddr) + if err != nil { + log.Println("dir", config.StoreAddr, "not found, so no backups") + return + } + conf := lastn.Config{ + N: 3, + Rclone: "/dev/null", + Root: realpath, + Ns: "backups", + Store: "files", + Conf: realpath + "-backups", + } + log.Printf("backups conf: %+v", conf) + lastn, err := lastn.New(conf) + if err != nil { + log.Println("backups disabled:", realpath, ":", err) + return + } + ticker := time.NewTicker(time.Hour * 24) + log.Println("backup initial:", err) + if err := lastn.Push(); err != nil { + log.Println("backup failed:", err) + } + for _ = range ticker.C { + log.Println("backing up...") + if err := lastn.Push(); err != nil { + log.Println("backup push failed:", err) + } + if err := lastn.Clean(); err != nil { + log.Println("backup clean failed:", err) + } + } +} diff --git a/server/.notes/create.go b/server/.notes/create.go new file mode 100755 index 0000000..ebf6ec1 --- /dev/null +++ b/server/.notes/create.go @@ -0,0 +1,24 @@ +package notes + +import ( + "html" + "local/notes-server/filetree" + "net/http" + "path" + "strings" +) + +func (n *Notes) Create(w http.ResponseWriter, r *http.Request) { + content := r.FormValue("base") + content = html.UnescapeString(content) + content = strings.ReplaceAll(content, "\r", "") + urlPath := path.Join(r.URL.Path, content) + p := filetree.NewPathFromURL(urlPath) + if p.IsDir() { + w.WriteHeader(http.StatusBadRequest) + return + } + url := *r.URL + url.Path = path.Join("/edit/", p.BaseHREF) + http.Redirect(w, r, url.String(), http.StatusSeeOther) +} diff --git a/server/.notes/create_test.go b/server/.notes/create_test.go new file mode 100755 index 0000000..6e8cef9 --- /dev/null +++ b/server/.notes/create_test.go @@ -0,0 +1 @@ +package notes diff --git a/server/.notes/dir.go b/server/.notes/dir.go new file mode 100755 index 0000000..9812e0c --- /dev/null +++ b/server/.notes/dir.go @@ -0,0 +1,37 @@ +package notes + +import ( + "fmt" + "io/ioutil" + "net/http" + "path" +) + +func notesDir(p Path, w http.ResponseWriter, r *http.Request) { + dirs, files := lsDir(p) + content := dirs.List() + notesDirHead(p, w) + block(content, w) + fmt.Fprintln(w, files.List()) +} + +func notesDirHead(p Path, w http.ResponseWriter) { + fmt.Fprintf(w, ` +
+ + +
+ `, path.Join("/create/", p.BaseHREF)) +} + +func lsDir(path Path) (Paths, Paths) { + dirs := newDirs() + files := newFiles() + + found, _ := ioutil.ReadDir(path.Local) + for _, f := range found { + dirs.Push(path, f) + files.Push(path, f) + } + return Paths(*dirs), Paths(*files) +} diff --git a/server/.notes/dir_test.go b/server/.notes/dir_test.go new file mode 100755 index 0000000..867b686 --- /dev/null +++ b/server/.notes/dir_test.go @@ -0,0 +1,26 @@ +package notes + +import ( + "net/http/httptest" + "testing" +) + +func TestLsDir(t *testing.T) { + p := Path{Local: "/usr/local"} + dirs, files := lsDir(p) + if len(dirs) == 0 { + t.Fatal(len(dirs)) + } + if len(files) == 0 { + t.Fatal(len(files)) + } + t.Log(dirs) + t.Log(files) +} + +func TestNotesDir(t *testing.T) { + path := Path{Local: "/usr/local"} + w := httptest.NewRecorder() + notesDir(path, w, nil) + t.Logf("%s", w.Body.Bytes()) +} diff --git a/server/.notes/edit.go b/server/.notes/edit.go new file mode 100755 index 0000000..794bbdd --- /dev/null +++ b/server/.notes/edit.go @@ -0,0 +1,43 @@ +package notes + +import ( + "fmt" + "io/ioutil" + "net/http" + "strings" +) + +func (s *Server) edit(w http.ResponseWriter, r *http.Request) { + p := NewPathFromURL(r.URL.Path) + if p.IsDir() { + http.NotFound(w, r) + return + } + head(w, r) + editHead(w, p) + editFile(w, p) + foot(w, r) +} + +func editHead(w http.ResponseWriter, p Path) { + fmt.Fprintln(w, h2(p.MultiLink())) +} + +func editFile(w http.ResponseWriter, p Path) { + href := p.HREF + href = strings.TrimPrefix(href, "/") + hrefs := strings.SplitN(href, "/", 2) + href = hrefs[0] + if len(hrefs) > 1 { + href = hrefs[1] + } + b, _ := ioutil.ReadFile(p.Local) + fmt.Fprintf(w, ` +
+ + +
+ +
+ `, href, b) +} diff --git a/server/.notes/edit_test.go b/server/.notes/edit_test.go new file mode 100755 index 0000000..6e8cef9 --- /dev/null +++ b/server/.notes/edit_test.go @@ -0,0 +1 @@ +package notes diff --git a/server/.notes/file.go b/server/.notes/file.go new file mode 100755 index 0000000..c372640 --- /dev/null +++ b/server/.notes/file.go @@ -0,0 +1,29 @@ +package notes + +import ( + "fmt" + "io/ioutil" + "net/http" + "path" + + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" +) + +func notesFile(p Path, w http.ResponseWriter, r *http.Request) { + b, _ := ioutil.ReadFile(p.Local) + notesFileHead(p, w) + renderer := html.NewRenderer(html.RendererOptions{ + Flags: html.CommonFlags | html.TOC, + }) + parser := parser.NewWithExtensions(parser.CommonExtensions | parser.HeadingIDs | parser.AutoHeadingIDs | parser.Titleblock) + content := markdown.ToHTML(b, parser, renderer) + fmt.Fprintf(w, "%s\n", content) +} + +func notesFileHead(p Path, w http.ResponseWriter) { + fmt.Fprintf(w, ` + + `, path.Join("/edit/", p.BaseHREF)) +} diff --git a/server/.notes/file_test.go b/server/.notes/file_test.go new file mode 100755 index 0000000..3d22e3e --- /dev/null +++ b/server/.notes/file_test.go @@ -0,0 +1,46 @@ +package notes + +import ( + "fmt" + "io/ioutil" + "net/http/httptest" + "os" + "strings" + "testing" +) + +func TestNotesFile(t *testing.T) { + f, err := ioutil.TempFile(os.TempDir(), "until*") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + fmt.Fprintln(f, ` +# Hello +## World +* This +* is +* bullets + +| My | table | goes | +|----|-------|------| +| h | e | n | + + `) + f.Close() + w := httptest.NewRecorder() + p := Path{Local: f.Name()} + notesFile(p, w, nil) + s := string(w.Body.Bytes()) + shouldContain := []string{ + "tbody", + "h1", + "h2", + } + for _, should := range shouldContain { + if !strings.Contains(s, should) { + t.Fatalf("%s: %s", should, s) + } + } + t.Logf("%s", s) +} diff --git a/server/.notes/notes.go b/server/.notes/notes.go new file mode 100755 index 0000000..7a676ab --- /dev/null +++ b/server/.notes/notes.go @@ -0,0 +1,13 @@ +package notes + +import "local/notes-server/config" + +type Notes struct { + root string +} + +func New() *Notes { + return &Notes{ + root: config.Root, + } +} diff --git a/server/.notes/rnotes.go b/server/.notes/rnotes.go new file mode 100755 index 0000000..7518bfd --- /dev/null +++ b/server/.notes/rnotes.go @@ -0,0 +1,27 @@ +package notes + +import ( + "fmt" + "net/http" +) + +func (s *Server) notes(w http.ResponseWriter, r *http.Request) { + p := NewPathFromURL(r.URL.Path) + if p.IsDir() { + head(w, r) + notesHead(w, p) + notesDir(p, w, r) + foot(w, r) + } else if p.IsFile() { + head(w, r) + notesHead(w, p) + notesFile(p, w, r) + foot(w, r) + } else { + http.NotFound(w, r) + } +} + +func notesHead(w http.ResponseWriter, p Path) { + fmt.Fprintln(w, h2(p.MultiLink())) +} diff --git a/server/.notes/rnotes_test.go b/server/.notes/rnotes_test.go new file mode 100755 index 0000000..6e8cef9 --- /dev/null +++ b/server/.notes/rnotes_test.go @@ -0,0 +1 @@ +package notes diff --git a/server/.notes/submit.go b/server/.notes/submit.go new file mode 100755 index 0000000..fc53014 --- /dev/null +++ b/server/.notes/submit.go @@ -0,0 +1,31 @@ +package notes + +import ( + "fmt" + "html" + "io/ioutil" + "net/http" + "os" + "path" + "strings" +) + +func (s *Server) submit(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.NotFound(w, r) + return + } + content := r.FormValue("content") + content = html.UnescapeString(content) + content = strings.ReplaceAll(content, "\r", "") + p := NewPathFromURL(r.URL.Path) + os.MkdirAll(path.Dir(p.Local), os.ModePerm) + if err := ioutil.WriteFile(p.Local, []byte(content), os.ModePerm); err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, err) + } else { + url := *r.URL + url.Path = path.Join("/notes/", p.BaseHREF) + http.Redirect(w, r, url.String(), http.StatusSeeOther) + } +} diff --git a/server/.notes/submit_test.go b/server/.notes/submit_test.go new file mode 100755 index 0000000..6e8cef9 --- /dev/null +++ b/server/.notes/submit_test.go @@ -0,0 +1 @@ +package notes diff --git a/server/routes.go b/server/routes.go new file mode 100755 index 0000000..90946b8 --- /dev/null +++ b/server/routes.go @@ -0,0 +1,43 @@ +package server + +import ( + "fmt" + "local/gziphttp" + "local/router" + "net/http" + "path/filepath" +) + +func (s *Server) Routes() error { + wildcard := router.Wildcard + endpoints := []struct { + path string + handler http.HandlerFunc + }{ + { + path: fmt.Sprintf("%s%s", wildcard, wildcard), + handler: s.gzip(s.authenticate(http.NotFound)), + }, + } + + for _, endpoint := range endpoints { + if err := s.Add(endpoint.path, endpoint.handler); err != nil { + return err + } + } + return nil +} + +func (s *Server) gzip(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if gziphttp.Can(r) { + gz := gziphttp.New(w) + defer gz.Close() + w = gz + } + if filepath.Ext(r.URL.Path) == ".css" { + w.Header().Set("Content-Type", "text/css; charset=utf-8") + } + h(w, r) + } +} diff --git a/server/server.go b/server/server.go new file mode 100755 index 0000000..ee05aeb --- /dev/null +++ b/server/server.go @@ -0,0 +1,32 @@ +package server + +import ( + "local/firestormy/config" + "local/oauth2/oauth2client" + "local/router" + "log" + "net/http" +) + +type Server struct { + *router.Router +} + +func New() *Server { + return &Server{ + Router: router.New(), + } +} + +func (s *Server) authenticate(foo http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if config.OAuthServer != "" { + err := oauth2client.Authenticate(config.OAuthServer, "firestormy", w, r) + if err != nil { + log.Println(err) + return + } + } + foo(w, r) + } +}