Browse Source

note editing implemented

master
Christian Müller 8 years ago
parent
commit
59aeab60c1
  1. 13
      assets/templates/form.html
  2. 4
      assets/templates/stats.html
  3. 29
      server.go
  4. 56
      storage.go

13
assets/public/new.html → assets/templates/form.html

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
{{define "Form"}}
<!DOCTYPE html>
<html>
<head>
<title>NoteHub &mdash; Add note</title>
<title>NoteHub &mdash; {{if .ID}}Edit{{else}}Add{{end}} note</title>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="/style.css" rel="stylesheet" type="text/css" />
@ -9,13 +10,10 @@ @@ -9,13 +10,10 @@
</head>
<body>
<form action="/note" autocomplete="off" method="POST" target="_self">
<textarea autofocus name="text"></textarea>
<textarea autofocus name="text">{{.Text}}</textarea>
<fieldset>
<input class="ui-elem"
name="plain-password"
placeholder="Password for editing"
type="text"
autocomplete="off">
<input id="id" name="id" value="{{.ID}}" type="hidden" />
<input class="ui-elem" id="password" name="password" placeholder="Password for editing" type="text" autocomplete="off">
<label style="margin-right: 1em">
<input id="tos" name="tos" type="checkbox" onClick="toggleButton()">
Accept <a href="/TOS.md">Terms of Service</a>
@ -33,3 +31,4 @@ @@ -33,3 +31,4 @@
</script>
</body>
</html>
{{end}}

4
assets/templates/stats.html

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
{{define "Stats"}}
<h2>Statistics</h2>
<table>
<tr><td>Published</td><td>{{.Published}}</td></tr>
<tr><td>Edited</td><td>{{.Edited}}</td></tr>
<tr><td>Published</td><td>{{.Published.Format "Jan 02, 2006 15:04:05 UTC"}}</td></tr>
<tr><td>Edited</td><td>{{.Edited.Format "Jan 02, 2006 15:04:05 UTC"}}</td></tr>
<tr><td>Views</td><td>{{.Views}}</td></tr>
</table>
{{end}}

29
server.go

@ -35,21 +35,24 @@ func main() { @@ -35,21 +35,24 @@ func main() {
e.File("/robots.txt", "assets/public/robots.txt")
e.File("/style.css", "assets/public/style.css")
e.File("/index.html", "assets/public/index.html")
e.File("/new", "assets/public/new.html")
e.File("/", "assets/public/index.html")
e.GET("/TOS.md", func(c echo.Context) error {
n, code := md2html(c, "TOS")
return c.Render(code, "Page", n)
})
e.GET("/:id", func(c echo.Context) error {
n, code := load(c, db)
n.prepare()
return c.Render(code, "Note", n)
})
e.GET("/:id/export", func(c echo.Context) error {
n, code := load(c, db)
return c.String(code, n.Text)
})
e.GET("/:id/stats", func(c echo.Context) error {
n, code := load(c, db)
buf := bytes.NewBuffer([]byte{})
@ -58,6 +61,15 @@ func main() { @@ -58,6 +61,15 @@ func main() {
return c.Render(code, "Note", n)
})
e.GET("/:id/edit", func(c echo.Context) error {
n, code := load(c, db)
return c.Render(code, "Form", n)
})
e.GET("/new", func(c echo.Context) error {
return c.Render(http.StatusOK, "Form", nil)
})
e.POST("/note", func(c echo.Context) error {
vals, err := c.FormParams()
if err != nil {
@ -73,18 +85,25 @@ func main() { @@ -73,18 +85,25 @@ func main() {
return c.Render(code, "Note",
errPage(code, "note length not accepted"))
}
n := Note{
id := get(vals, "id")
n := &Note{
ID: id,
Text: text,
Password: get(vals, "password"),
}
id, err := save(c, db, &n)
n, err = save(c, db, n)
if err != nil {
c.Logger().Error(err)
code := http.StatusServiceUnavailable
if err == errorUnathorised {
code = http.StatusUnauthorized
} else if err == errorBadRequest {
code = http.StatusBadRequest
}
return c.Render(code, "Note", errPage(code))
}
c.Logger().Infof("new note %q created", n.ID)
return c.Redirect(http.StatusMovedPermanently, "/"+id)
c.Logger().Infof("note %q saved", n.ID)
return c.Redirect(http.StatusMovedPermanently, "/"+n.ID)
})
e.Logger.Fatal(e.Start(":3000"))

56
storage.go

@ -2,7 +2,9 @@ package main @@ -2,7 +2,9 @@ package main
import (
"bytes"
"crypto/sha256"
"database/sql"
"errors"
"fmt"
"html/template"
"math/rand"
@ -24,12 +26,17 @@ const idLength = 5 @@ -24,12 +26,17 @@ const idLength = 5
var (
errorCodes = map[int]string{
400: "Bad request",
401: "Unauthorized",
404: "Not found",
412: "Precondition failed",
503: "Service unavailable",
}
rexpNewLine = regexp.MustCompile("[\n\r]")
rexpNonAlphaNum = regexp.MustCompile("[`~!@#$%^&*_|+=?;:'\",.<>{}\\/]")
rexpNoScriptIframe = regexp.MustCompile("<.*?(iframe|script).*?>")
errorUnathorised = errors.New("id or password is wrong")
errorBadRequest = errors.New("password is empty")
)
type Note struct {
@ -50,35 +57,69 @@ func errPage(code int, details ...string) Note { @@ -50,35 +57,69 @@ func errPage(code int, details ...string) Note {
}
}
func (n *Note) render() {
func (n *Note) prepare() {
fstLine := rexpNewLine.Split(n.Text, -1)[0]
maxLength := 25
if len(fstLine) < 25 {
maxLength = len(fstLine)
}
n.Text = rexpNoScriptIframe.ReplaceAllString(n.Text, "")
n.Title = strings.TrimSpace(rexpNonAlphaNum.ReplaceAllString(fstLine[:maxLength], ""))
n.Password = ""
n.Content = mdTmplHTML([]byte(n.Text))
}
func save(c echo.Context, db *sql.DB, n *Note) (string, error) {
func save(c echo.Context, db *sql.DB, n *Note) (*Note, error) {
if n.Password != "" {
n.Password = fmt.Sprintf("%x", sha256.Sum256([]byte(n.Password)))
}
if n.ID == "" {
return insert(c, db, n)
}
return update(c, db, n)
}
func update(c echo.Context, db *sql.DB, n *Note) (*Note, error) {
if n.Password == "" {
return nil, errorBadRequest
}
tx, err := db.Begin()
if err != nil {
return "", err
return nil, err
}
stmt, _ := tx.Prepare("update notes set (text, edited) = (?, ?) where id = ? and password = ?")
defer stmt.Close()
res, err := stmt.Exec(n.Text, time.Now(), n.ID, n.Password)
if err != nil {
tx.Rollback()
return nil, err
}
rows, err := res.RowsAffected()
if rows != 1 {
tx.Rollback()
return nil, errorUnathorised
}
return n, tx.Commit()
}
func insert(c echo.Context, db *sql.DB, n *Note) (*Note, error) {
tx, err := db.Begin()
if err != nil {
return nil, err
}
stmt, _ := tx.Prepare("insert into notes(id, text, password) values(?, ?, ?)")
defer stmt.Close()
id := randId()
_, err = stmt.Exec(id, n.Text, n.Password)
if err != nil {
if strings.HasPrefix(err.Error(), "UNIQUE constraint failed") {
tx.Rollback()
if strings.HasPrefix(err.Error(), "UNIQUE constraint failed") {
c.Logger().Infof("collision on id %q", id)
return save(c, db, n)
}
return "", err
return nil, err
}
return id, tx.Commit()
n.ID = id
return n, tx.Commit()
}
func randId() string {
@ -119,7 +160,6 @@ func load(c echo.Context, db *sql.DB) (Note, int) { @@ -119,7 +160,6 @@ func load(c echo.Context, db *sql.DB) (Note, int) {
Published: published,
Edited: edited,
}
n.render()
return *n, http.StatusOK
}

Loading…
Cancel
Save