refactoring
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
-86
@@ -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"
|
||||
)
|
||||
|
||||
@@ -25,28 +20,9 @@ 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")
|
||||
)
|
||||
|
||||
type Note struct {
|
||||
ID, Title, Text, Password string
|
||||
Published, Edited time.Time
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user