diff --git a/render.go b/render.go new file mode 100644 index 0000000..3299898 --- /dev/null +++ b/render.go @@ -0,0 +1,75 @@ +package main + +import ( + "errors" + "fmt" + "html/template" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/golang-commonmark/markdown" + "github.com/labstack/echo" +) + +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).*?>") + rexpLink = regexp.MustCompile("(ht|f)tp://[^\\s]+") + + errorUnathorised = errors.New("id or password is wrong") + errorBadRequest = errors.New("password is empty") +) + +func errPage(code int, details ...string) *Note { + text := errorCodes[code] + body := text + if len(details) > 0 { + body += ": " + strings.Join(details, ";") + } + n := &Note{ + Title: text, + Text: fmt.Sprintf("# %d %s", code, body), + } + n.prepare() + return n +} + +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.Content = mdTmplHTML([]byte(n.Text)) +} + +var mdRenderer = markdown.New(markdown.HTML(true)) + +func mdTmplHTML(content []byte) template.HTML { + return template.HTML(mdRenderer.RenderToString(content)) +} + +func md2html(c echo.Context, name string) (*Note, int) { + path := "assets/markdown/" + name + ".md" + mdContent, err := ioutil.ReadFile(path) + if err != nil { + c.Logger().Errorf("couldn't open markdown page %q: %v", path, err) + code := http.StatusServiceUnavailable + return errPage(code), code + } + c.Logger().Debugf("rendering markdown page %q", name) + return &Note{Title: name, Content: mdTmplHTML(mdContent)}, http.StatusOK +} diff --git a/server.go b/server.go index 462d829..4e70eb1 100644 --- a/server.go +++ b/server.go @@ -5,6 +5,7 @@ import ( "html/template" "io" "io/ioutil" + "math" "net/http" "net/url" "os" @@ -74,7 +75,7 @@ func main() { } } defer stats.Store(n.ID, views+1) - if n.Fraud() { + if fraudelent(n) { n.Ads = mdTmplHTML(ads) } c.Logger().Debugf("/%q requested; response code: %d", n.ID, code) @@ -160,14 +161,10 @@ func get(vals url.Values, key string) string { return "" } -func md2html(c echo.Context, name string) (*Note, int) { - path := "assets/markdown/" + name + ".md" - mdContent, err := ioutil.ReadFile(path) - if err != nil { - c.Logger().Errorf("couldn't open markdown page %q: %v", path, err) - code := http.StatusServiceUnavailable - return errPage(code), code - } - c.Logger().Debugf("rendering markdown page %q", name) - return &Note{Title: name, Content: mdTmplHTML(mdContent)}, http.StatusOK +func fraudelent(n *Note) bool { + stripped := rexpLink.ReplaceAllString(n.Text, "") + l1 := len(n.Text) + l2 := len(stripped) + return n.Views > 100 && + int(math.Ceil(100*float64(l1-l2)/float64(l1))) > fraudThreshold } diff --git a/stats.go b/stats.go new file mode 100644 index 0000000..5a8f2e0 --- /dev/null +++ b/stats.go @@ -0,0 +1,35 @@ +package main + +import ( + "database/sql" + "sync" + "time" + + "github.com/labstack/echo" +) + +const statsSavingInterval = 1 * time.Minute + +func persistStats(logger echo.Logger, db *sql.DB, stats *sync.Map) { + for { + time.Sleep(statsSavingInterval) + tx, err := db.Begin() + if err != nil { + logger.Error(err) + return + } + c := 0 + stats.Range(func(id, views interface{}) bool { + stmt, _ := tx.Prepare("update notes set views = ? where id = ?") + _, err := stmt.Exec(views, id) + if err == nil { + c++ + } + stmt.Close() + defer stats.Delete(id) + return true + }) + tx.Commit() + logger.Infof("successfully persisted %d values", c) + } +} diff --git a/storage.go b/storage.go index 26456b2..bdee77e 100644 --- a/storage.go +++ b/storage.go @@ -4,18 +4,13 @@ import ( "bytes" "crypto/sha256" "database/sql" - "errors" "fmt" "html/template" - "math" "math/rand" "net/http" - "regexp" "strings" - "sync" "time" - "github.com/golang-commonmark/markdown" "github.com/labstack/echo" ) @@ -24,27 +19,8 @@ func init() { } const ( - idLength = 5 - statsSavingInterval = 1 * time.Minute - fraudThreshold = 7 -) - -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).*?>") - rexpLink = regexp.MustCompile("(ht|f)tp://[^\\s]+") - - errorUnathorised = errors.New("id or password is wrong") - errorBadRequest = errors.New("password is empty") + idLength = 5 + fraudThreshold = 7 ) type Note struct { @@ -54,55 +30,6 @@ type Note struct { Content, Ads template.HTML } -func errPage(code int, details ...string) *Note { - text := errorCodes[code] - body := text - if len(details) > 0 { - body += ": " + strings.Join(details, ";") - } - n := &Note{ - Title: text, - Text: fmt.Sprintf("# %d %s", code, body), - } - n.prepare() - return n -} - -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.Content = mdTmplHTML([]byte(n.Text)) -} - -func persistStats(logger echo.Logger, db *sql.DB, stats *sync.Map) { - for { - time.Sleep(statsSavingInterval) - tx, err := db.Begin() - if err != nil { - logger.Error(err) - return - } - c := 0 - stats.Range(func(id, views interface{}) bool { - stmt, _ := tx.Prepare("update notes set views = ? where id = ?") - _, err := stmt.Exec(views, id) - if err == nil { - c++ - } - stmt.Close() - defer stats.Delete(id) - return true - }) - tx.Commit() - logger.Infof("successfully persisted %d values", c) - } -} - 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))) @@ -201,16 +128,3 @@ func load(c echo.Context, db *sql.DB) (*Note, int) { } return n, http.StatusOK } - -var mdRenderer = markdown.New(markdown.HTML(true)) - -func mdTmplHTML(content []byte) template.HTML { - return template.HTML(mdRenderer.RenderToString(content)) -} - -func (n *Note) Fraud() bool { - stripped := rexpLink.ReplaceAllString(n.Text, "") - l1 := len(n.Text) - l2 := len(stripped) - return int(math.Ceil(100*float64(l1-l2)/float64(l1))) > fraudThreshold -}