* WIP: start mongo engine * WIP: mongo accessor and admin * integrate mongo store to main * disable mongo tests by default, only in CI * connection with constructor * add mongo buffered writer * buffered mongo writer * fix nil responses on an empty list from mongo * missing mongo index for scores * cancelable store * add gridfs implementation of avatar store * fix race on mongo session copy * gridfs avatars without tmp files * move avatar store * minor comments and refactoring for avatar store * merged from current master * simplify gridfs reader * lint: fix minor warns * test mongo against env defined url * pass MONGO_REMARK_TEST to docker and travis * set dockerfile env for mongo test url * increase connect timeout in mongo tests * pass MONGO_REMARK_TEST to drone build * add MONGO_REMARK_TEST to branch stage of drone * mass mongo test url via build_args_from_env * populate mongo IP to docker build hosts * test env * pass mongo ip via .mongo * remove .mongo temp from git * add .mongo -> env to linter step * allow more time to autoflush writer test * default mongo tests to "mongo" if not in env * merge fresh master into * add test for mongo cleanup * msg for a failed test * lazy fix for failed test * add an ability to skip all mongo tests * add backend dev instructions * remove unused code from mongo server * move mongo testing to connection_test * restore testing.go * lint: minor warns for testing code
145 lines
4.0 KiB
Go
145 lines
4.0 KiB
Go
package store
|
|
|
|
import (
|
|
"html/template"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
"github.com/microcosm-cc/bluemonday"
|
|
)
|
|
|
|
// Comment represents a single comment with optional reference to its parent
|
|
type Comment struct {
|
|
ID string `json:"id" bson:"_id"`
|
|
ParentID string `json:"pid"`
|
|
Text string `json:"text"`
|
|
Orig string `json:"orig,omitempty"`
|
|
User User `json:"user"`
|
|
Locator Locator `json:"locator"`
|
|
Score int `json:"score"`
|
|
Votes map[string]bool `json:"votes"`
|
|
Timestamp time.Time `json:"time" bson:"time"`
|
|
Edit *Edit `json:"edit,omitempty" bson:"edit,omitempty"` // pointer to have empty default in json response
|
|
Pin bool `json:"pin,omitempty" bson:"pin,omitempty"`
|
|
Deleted bool `json:"delete,omitempty" bson:"delete"`
|
|
}
|
|
|
|
// Locator keeps site and url of the post
|
|
type Locator struct {
|
|
SiteID string `json:"site,omitempty" bson:"site"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// Edit indication
|
|
type Edit struct {
|
|
Timestamp time.Time `json:"time" bson:"time"`
|
|
Summary string `json:"summary"`
|
|
}
|
|
|
|
// PostInfo holds summary for given post url
|
|
type PostInfo struct {
|
|
URL string `json:"url"`
|
|
Count int `json:"count"`
|
|
ReadOnly bool `json:"read_only,omitempty" bson:"read_only,omitempty"`
|
|
FirstTS time.Time `json:"first_time,omitempty" bson:"first_time,omitempty"`
|
|
LastTS time.Time `json:"last_time,omitempty" bson:"last_time,omitempty"`
|
|
}
|
|
|
|
// BlockedUser holds id and ts for blocked user
|
|
type BlockedUser struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Until time.Time `json:"time"`
|
|
}
|
|
|
|
// DeleteMode defines how much comment info will be erased
|
|
type DeleteMode int
|
|
|
|
// DeleteMode enum
|
|
const (
|
|
SoftDelete DeleteMode = 0
|
|
HardDelete DeleteMode = 1
|
|
)
|
|
|
|
// Maximum length for URL text shortening.
|
|
const shortURLLen = 48
|
|
|
|
// PrepareUntrusted pre-processes a comment received from untrusted source by clearing all
|
|
// autogen fields and reset everything users not supposed to provide
|
|
func (c *Comment) PrepareUntrusted() {
|
|
c.ID = "" // don't allow user to define ID, force auto-gen
|
|
c.Timestamp = time.Time{} // reset time, force auto-gen
|
|
c.Votes = make(map[string]bool)
|
|
c.Score = 0
|
|
c.Edit = nil
|
|
c.Pin = false
|
|
c.Deleted = false
|
|
}
|
|
|
|
// SetDeleted clears comment info, reset to deleted state. hard flag will clear all user info as well
|
|
func (c *Comment) SetDeleted(mode DeleteMode) {
|
|
c.Text = ""
|
|
c.Orig = ""
|
|
c.Score = 0
|
|
c.Votes = map[string]bool{}
|
|
c.Edit = nil
|
|
c.Deleted = true
|
|
c.Pin = false
|
|
|
|
if mode == HardDelete {
|
|
c.User.Name = "deleted"
|
|
c.User.ID = "deleted"
|
|
c.User.Picture = ""
|
|
c.User.IP = ""
|
|
}
|
|
}
|
|
|
|
// Sanitize clean dangerous html/js from the comment, shorten autolinks.
|
|
func (c *Comment) Sanitize() {
|
|
p := bluemonday.UGCPolicy()
|
|
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
|
|
c.Text = p.Sanitize(c.Text)
|
|
c.Text = shortenAutoLinks(c.Text, shortURLLen)
|
|
c.Orig = p.Sanitize(c.Orig)
|
|
c.User.ID = template.HTMLEscapeString(c.User.ID)
|
|
c.User.Name = template.HTMLEscapeString(c.User.Name)
|
|
c.User.Picture = p.Sanitize(c.User.Picture)
|
|
}
|
|
|
|
// Shortens all the automatic links in HTML: auto link has equal "href" and "text" attributes.
|
|
func shortenAutoLinks(commentHTML string, max int) (resHTML string) {
|
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(commentHTML))
|
|
if err != nil {
|
|
return commentHTML
|
|
}
|
|
doc.Find("a").Each(func(i int, s *goquery.Selection) {
|
|
if href, ok := s.Attr("href"); ok {
|
|
if href != s.Text() || len(href) < max+3 || max < 3 {
|
|
return
|
|
}
|
|
url, e := url.Parse(href)
|
|
if e != nil {
|
|
return
|
|
}
|
|
url.Path, url.RawQuery, url.Fragment = "", "", ""
|
|
host := url.String()
|
|
if host == "" {
|
|
return
|
|
}
|
|
short := href[:max-3]
|
|
if len(short) < len(host) {
|
|
short = host
|
|
}
|
|
s.SetText(short + "...")
|
|
}
|
|
})
|
|
resHTML, err = doc.Find("body").Html()
|
|
if err != nil {
|
|
return commentHTML
|
|
}
|
|
return resHTML
|
|
}
|