Split into packages only manually tested

master
Bel LaPointe 2019-11-09 18:51:03 -07:00
parent 0d497f4fa8
commit 60dc6bc876
48 changed files with 530 additions and 125 deletions

29
TODO
View File

@ -8,3 +8,32 @@ search
move auth as flag in router
. and ../** as roots cause bugs in listing and loading and linking
`create` at root is a 400, base= in URL (when `create` input is empty)
FTS
https://stackoverflow.com/questions/26709971/could-this-be-more-efficient-in-go
main test -
- create,
- header
- text box
- submit
- submit target
- edit,
- header
- text box
- submit
- submit target
- dir,
- header
- create
- create target
- list
- note,
- header
- edit
- edit target
- content
- root-> dir,
- root->file,
- dir->dir,
- dir->file

View File

@ -1,4 +1,4 @@
package server
package filetree
import (
"os"
@ -7,7 +7,7 @@ import (
type Dirs []Path
func newDirs() *Dirs {
func NewDirs() *Dirs {
d := Dirs([]Path{})
return &d
}

1
filetree/dirs_test.go Executable file
View File

@ -0,0 +1 @@
package filetree

View File

@ -1,4 +1,4 @@
package server
package filetree
import (
"os"
@ -7,7 +7,7 @@ import (
type Files []Path
func newFiles() *Files {
func NewFiles() *Files {
d := Files([]Path{})
return &d
}

1
filetree/files_test.go Executable file
View File

@ -0,0 +1 @@
package filetree

View File

@ -1,4 +1,4 @@
package server
package filetree
import (
"fmt"

View File

@ -1,4 +1,4 @@
package server
package filetree
import (
"local/notes-server/config"

View File

@ -1,4 +1,4 @@
package server
package filetree
type Paths []Path

1
filetree/paths_test.go Executable file
View File

@ -0,0 +1 @@
package filetree

15
notes/create.go Executable file
View File

@ -0,0 +1,15 @@
package notes
import (
"errors"
"local/notes-server/filetree"
"path"
)
func (n *Notes) Create(urlPath string) (string, error) {
p := filetree.NewPathFromURL(urlPath)
if p.IsDir() {
return "", errors.New("directory exists")
}
return path.Join("/edit/", p.BaseHREF), nil
}

1
notes/create_test.go Executable file
View File

@ -0,0 +1 @@
package notes

28
notes/dir.go Executable file
View File

@ -0,0 +1,28 @@
package notes
import (
"errors"
"io/ioutil"
"local/notes-server/filetree"
)
func (n *Notes) Dir(urlPath string) (string, string, error) {
p := filetree.NewPathFromURL(urlPath)
if !p.IsDir() {
return "", "", errors.New("not a dir")
}
dirs, files := n.lsDir(p)
return dirs.List(), files.List(), nil
}
func (n *Notes) lsDir(path filetree.Path) (filetree.Paths, filetree.Paths) {
dirs := filetree.NewDirs()
files := filetree.NewFiles()
found, _ := ioutil.ReadDir(path.Local)
for _, f := range found {
dirs.Push(path, f)
files.Push(path, f)
}
return filetree.Paths(*dirs), filetree.Paths(*files)
}

36
notes/dir_test.go Executable file
View File

@ -0,0 +1,36 @@
package notes
import (
"local/notes-server/config"
"testing"
)
func TestDir(t *testing.T) {
n := &Notes{}
config.Root = "/"
dirs, files, err := n.Dir("/notes/usr/local")
if err != nil {
t.Fatal(err)
}
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) {
n := &Notes{}
body, body2, err := n.Dir("/notes/usr/local")
if err != nil {
t.Fatal(err)
}
if body == "" || body2 == "" {
t.Fatal(body, body2)
}
t.Logf("%s", body)
t.Logf("%s", body2)
}

36
notes/edit.go Executable file
View File

@ -0,0 +1,36 @@
package notes
import (
"errors"
"fmt"
"io/ioutil"
"local/notes-server/filetree"
"strings"
)
func (n *Notes) Edit(urlPath string) (string, error) {
p := filetree.NewPathFromURL(urlPath)
if p.IsDir() {
return "", errors.New("path is dir")
}
return editFile(p), nil
}
func editFile(p filetree.Path) string {
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)
return fmt.Sprintf(`
<form action="/submit/%s" method="post" style="width:100%%; height: 90%%">
<table style="width:100%%; height: 90%%">
<textarea name="content" style="width:100%%; min-height:90%%">%s</textarea>
</table>
<button type="submit">Submit</button>
</form>
`, href, b)
}

1
notes/edit_test.go Executable file
View File

@ -0,0 +1 @@
package notes

25
notes/file.go Executable file
View File

@ -0,0 +1,25 @@
package notes
import (
"errors"
"io/ioutil"
"local/notes-server/filetree"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
func (n *Notes) File(urlPath string) (string, error) {
p := filetree.NewPathFromURL(urlPath)
if p.IsDir() {
return "", errors.New("path is dir")
}
b, _ := ioutil.ReadFile(p.Local)
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)
return string(content) + "\n", nil
}

49
notes/file_test.go Executable file
View File

@ -0,0 +1,49 @@
package notes
import (
"fmt"
"io/ioutil"
"local/notes-server/config"
"os"
"path"
"strings"
"testing"
)
func TestFile(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()
n := &Notes{}
config.Root = "/"
s, err := n.File(path.Join("notes", f.Name()))
if err != nil {
t.Fatal(err)
}
shouldContain := []string{
"tbody",
"h1",
"h2",
}
for _, should := range shouldContain {
if !strings.Contains(s, should) {
t.Fatalf("%s: %s", should, s)
}
}
t.Logf("%s", s)
}

13
notes/notes.go Normal file
View File

@ -0,0 +1,13 @@
package notes
import "local/notes-server/config"
type Notes struct {
root string
}
func New() *Notes {
return &Notes{
root: config.Root,
}
}

14
notes/submit.go Executable file
View File

@ -0,0 +1,14 @@
package notes
import (
"io/ioutil"
"local/notes-server/filetree"
"os"
"path"
)
func (n *Notes) Submit(urlPath, content string) error {
p := filetree.NewPathFromURL(urlPath)
os.MkdirAll(path.Dir(p.Local), os.ModePerm)
return ioutil.WriteFile(p.Local, []byte(content), os.ModePerm)
}

1
notes/submit_test.go Executable file
View File

@ -0,0 +1 @@
package notes

24
server/.notes/create.go Executable file
View File

@ -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)
}

1
server/.notes/create_test.go Executable file
View File

@ -0,0 +1 @@
package notes

View File

@ -1,4 +1,4 @@
package server
package notes
import (
"fmt"

View File

@ -1,4 +1,4 @@
package server
package notes
import (
"net/http/httptest"

43
server/.notes/edit.go Executable file
View File

@ -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, `
<form action="/submit/%s" method="post" style="width:100%%; height: 90%%">
<table style="width:100%%; height: 90%%">
<textarea name="content" style="width:100%%; min-height:90%%">%s</textarea>
</table>
<button type="submit">Submit</button>
</form>
`, href, b)
}

1
server/.notes/edit_test.go Executable file
View File

@ -0,0 +1 @@
package notes

View File

@ -1,4 +1,4 @@
package server
package notes
import (
"fmt"

View File

@ -1,4 +1,4 @@
package server
package notes
import (
"fmt"

13
server/.notes/notes.go Normal file
View File

@ -0,0 +1,13 @@
package notes
import "local/notes-server/config"
type Notes struct {
root string
}
func New() *Notes {
return &Notes{
root: config.Root,
}
}

27
server/.notes/rnotes.go Executable file
View File

@ -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()))
}

1
server/.notes/rnotes_test.go Executable file
View File

@ -0,0 +1 @@
package notes

31
server/.notes/submit.go Executable file
View File

@ -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)
}
}

1
server/.notes/submit_test.go Executable file
View File

@ -0,0 +1 @@
package notes

View File

@ -12,12 +12,12 @@ func (s *Server) create(w http.ResponseWriter, r *http.Request) {
content = html.UnescapeString(content)
content = strings.ReplaceAll(content, "\r", "")
urlPath := path.Join(r.URL.Path, content)
p := NewPathFromURL(urlPath)
if p.IsDir() {
w.WriteHeader(http.StatusBadRequest)
url := *r.URL
path, err := s.Notes.Create(urlPath)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
url := *r.URL
url.Path = path.Join("/edit/", p.BaseHREF)
url.Path = path
http.Redirect(w, r, url.String(), http.StatusSeeOther)
}

View File

@ -1 +0,0 @@
package server

View File

@ -1 +0,0 @@
package server

View File

@ -2,42 +2,22 @@ package server
import (
"fmt"
"io/ioutil"
"local/notes-server/filetree"
"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)
head(w, r)
editHead(w, filetree.NewPathFromURL(r.URL.Path))
edit, err := s.Notes.Edit(r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
head(w, r)
editHead(w, p)
editFile(w, p)
fmt.Fprintln(w, edit)
foot(w, r)
}
func editHead(w http.ResponseWriter, p Path) {
func editHead(w http.ResponseWriter, p filetree.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, `
<form action="/submit/%s" method="post" style="width:100%%; height: 90%%">
<table style="width:100%%; height: 90%%">
<textarea name="content" style="width:100%%; min-height:90%%">%s</textarea>
</table>
<button type="submit">Submit</button>
</form>
`, href, b)
}

View File

@ -1 +0,0 @@
package server

View File

@ -1 +0,0 @@
package server

43
server/html.go Normal file
View File

@ -0,0 +1,43 @@
package server
import (
"fmt"
"local/notes-server/config"
"net/http"
)
func head(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(config.Head + "\n"))
}
func foot(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(config.Foot + "\n"))
}
func block(w http.ResponseWriter, content string) {
fmt.Fprintf(w, "\n<div>\n%s\n</div>\n", content)
}
func h1(content string) string {
return h("1", content)
}
func h2(content string) string {
return h("2", content)
}
func h3(content string) string {
return h("3", content)
}
func h4(content string) string {
return h("4", content)
}
func h5(content string) string {
return h("5", content)
}
func h(level, content string) string {
return fmt.Sprintf("\n<h%s>\n%s\n</h%s>\n", level, content, level)
}

View File

@ -28,7 +28,7 @@ func TestFoot(t *testing.T) {
func TestBlock(t *testing.T) {
w := httptest.NewRecorder()
block("hi", w)
block(w, "hi")
s := strings.ReplaceAll(strings.TrimSpace(string(w.Body.Bytes())), "\n", ".")
if ok, err := regexp.MatchString("<div>.*hi.*<.div>", s); err != nil {
t.Fatal(err, s)

55
server/notes.go Executable file → Normal file
View File

@ -2,26 +2,61 @@ package server
import (
"fmt"
"local/notes-server/filetree"
"net/http"
"path"
)
func (s *Server) notes(w http.ResponseWriter, r *http.Request) {
p := NewPathFromURL(r.URL.Path)
p := filetree.NewPathFromURL(r.URL.Path)
head(w, r)
notesHead(w, p)
defer foot(w, r)
if p.IsDir() {
head(w, r)
notesHead(w, p)
notesDir(p, w, r)
foot(w, r)
s.dir(w, r)
} else if p.IsFile() {
head(w, r)
notesHead(w, p)
notesFile(p, w, r)
foot(w, r)
s.file(w, r)
} else {
http.NotFound(w, r)
}
}
func notesHead(w http.ResponseWriter, p Path) {
func notesHead(w http.ResponseWriter, p filetree.Path) {
fmt.Fprintln(w, h2(p.MultiLink()))
}
func (s *Server) dir(w http.ResponseWriter, r *http.Request) {
dirs, files, err := s.Notes.Dir(r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
dirHead(w, filetree.NewPathFromURL(r.URL.Path).BaseHREF)
block(w, dirs)
block(w, files)
}
func dirHead(w http.ResponseWriter, baseHREF string) {
fmt.Fprintf(w, `
<form action=%q method="get">
<input type="text" name="base"></input>
<button type="submit">Create</button>
</form>
`, path.Join("/create/", baseHREF))
}
func (s *Server) file(w http.ResponseWriter, r *http.Request) {
file, err := s.Notes.File(r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fileHead(w, filetree.NewPathFromURL(r.URL.Path).BaseHREF)
fmt.Fprintln(w, file)
}
func fileHead(w http.ResponseWriter, baseHREF string) {
fmt.Fprintf(w, `
<a href=%q><input type="button" value="Edit"></input></a>
`, path.Join("/edit/", baseHREF))
}

View File

@ -1 +0,0 @@
package server

View File

@ -1 +0,0 @@
package server

View File

@ -2,10 +2,7 @@ package server
import (
"fmt"
"local/notes-server/config"
"local/oauth2/oauth2client"
"local/router"
"log"
"net/http"
)
@ -40,16 +37,3 @@ func (s *Server) Routes() error {
}
return nil
}
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, "notes-server", w, r)
if err != nil {
log.Println(err)
return
}
}
foo(w, r)
}
}

View File

@ -1,54 +1,35 @@
package server
import (
"fmt"
"local/notes-server/config"
"local/notes-server/notes"
"local/oauth2/oauth2client"
"local/router"
"log"
"net/http"
)
type Server struct {
*router.Router
Notes *notes.Notes
}
func New() *Server {
return &Server{
Router: router.New(),
Notes: notes.New(),
}
}
func head(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(config.Head + "\n"))
}
func foot(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(config.Foot + "\n"))
}
func block(content string, w http.ResponseWriter) {
fmt.Fprintf(w, "\n<div>\n%s\n</div>\n", content)
}
func h1(content string) string {
return h("1", content)
}
func h2(content string) string {
return h("2", content)
}
func h3(content string) string {
return h("3", content)
}
func h4(content string) string {
return h("4", content)
}
func h5(content string) string {
return h("5", content)
}
func h(level, content string) string {
return fmt.Sprintf("\n<h%s>\n%s\n</h%s>\n", level, content, level)
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, "notes-server", w, r)
if err != nil {
log.Println(err)
return
}
}
foo(w, r)
}
}

View File

@ -1,11 +1,9 @@
package server
import (
"fmt"
"html"
"io/ioutil"
"local/notes-server/filetree"
"net/http"
"os"
"path"
"strings"
)
@ -18,14 +16,12 @@ func (s *Server) submit(w http.ResponseWriter, r *http.Request) {
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)
err := s.Notes.Submit(r.URL.Path, content)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
url := *r.URL
url.Path = path.Join("/notes/", filetree.NewPathFromURL(r.URL.Path).BaseHREF)
http.Redirect(w, r, url.String(), http.StatusSeeOther)
}

View File

@ -1 +0,0 @@
package server