Variables which are computed multiple times in templates should only be computed once now.

Enabled the Watchdog Goroutine.
Moved a couple of parser tests into their own file.
Eliminated the hard-coded URLs in TestParser()
Refactored the alert system so that a specific actor is only loaded once rather than once for each alert. This isn't a problem in practice due to the user cache, but it might be on high traffic sites.
Don't run HandleServerSync() on single server setups.
Don't load the user in GetTopicUser(), if the topic creator is the current user.
Fixed a bug in the template generator where endloop nodes were being pushed instead of endif ones.
This commit is contained in:
Azareal 2018-11-22 17:21:43 +10:00
parent 87b3f9107d
commit 52c8be4173
13 changed files with 412 additions and 227 deletions

View File

@ -12,10 +12,21 @@ import (
"strconv"
"strings"
"github.com/Azareal/Gosora/query_gen"
"github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen"
)
type Alert struct {
ASID int
ActorID int
TargetUserID int
Event string
ElementType string
ElementID int
Actor *User
}
type AlertStmts struct {
addActivity *sql.Stmt
notifyWatchers *sql.Stmt
@ -50,81 +61,82 @@ func escapeTextInJson(in string) string {
return strings.Replace(in, "/", "\\/", -1)
}
func BuildAlert(asid int, event string, elementType string, actorID int, targetUserID int, elementID int, user User /* The current user */) (string, error) {
func BuildAlert(alert Alert, user User /* The current user */) (out string, err error) {
var targetUser *User
actor, err := Users.Get(actorID)
if err != nil {
return "", errors.New(phrases.GetErrorPhrase("alerts_no_actor"))
if alert.Actor == nil {
alert.Actor, err = Users.Get(alert.ActorID)
if err != nil {
return "", errors.New(phrases.GetErrorPhrase("alerts_no_actor"))
}
}
/*if elementType != "forum" {
targetUser, err = users.Get(targetUserID)
/*if alert.ElementType != "forum" {
targetUser, err = users.Get(alert.TargetUserID)
if err != nil {
LocalErrorJS("Unable to find the target user",w,r)
return
}
}*/
if event == "friend_invite" {
return buildAlertString(phrases.GetTmplPhrase("alerts.new_friend_invite"), []string{actor.Name}, actor.Link, actor.Avatar, asid), nil
if alert.Event == "friend_invite" {
return buildAlertString(phrases.GetTmplPhrase("alerts.new_friend_invite"), []string{alert.Actor.Name}, alert.Actor.Link, alert.Actor.Avatar, alert.ASID), nil
}
// Not that many events for us to handle in a forum
if elementType == "forum" {
if event == "reply" {
topic, err := Topics.Get(elementID)
if alert.ElementType == "forum" {
if alert.Event == "reply" {
topic, err := Topics.Get(alert.ElementID)
if err != nil {
DebugLogf("Unable to find linked topic %d", elementID)
DebugLogf("Unable to find linked topic %d", alert.ElementID)
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic"))
}
// Store the forum ID in the targetUser column instead of making a new one? o.O
// Add an additional column for extra information later on when we add the ability to link directly to posts. We don't need the forum data for now...
return buildAlertString(phrases.GetTmplPhrase("alerts.forum_new_topic"), []string{actor.Name, topic.Title}, topic.Link, actor.Avatar, asid), nil
return buildAlertString(phrases.GetTmplPhrase("alerts.forum_new_topic"), []string{alert.Actor.Name, topic.Title}, topic.Link, alert.Actor.Avatar, alert.ASID), nil
}
return buildAlertString(phrases.GetTmplPhrase("alerts.forum_unknown_action"), []string{actor.Name}, "", actor.Avatar, asid), nil
return buildAlertString(phrases.GetTmplPhrase("alerts.forum_unknown_action"), []string{alert.Actor.Name}, "", alert.Actor.Avatar, alert.ASID), nil
}
var url, area string
var phraseName = "alerts." + elementType
switch elementType {
var phraseName = "alerts." + alert.ElementType
switch alert.ElementType {
case "topic":
topic, err := Topics.Get(elementID)
topic, err := Topics.Get(alert.ElementID)
if err != nil {
DebugLogf("Unable to find linked topic %d", elementID)
DebugLogf("Unable to find linked topic %d", alert.ElementID)
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic"))
}
url = topic.Link
area = topic.Title
if targetUserID == user.ID {
if alert.TargetUserID == user.ID {
phraseName += "_own"
}
case "user":
targetUser, err = Users.Get(elementID)
targetUser, err = Users.Get(alert.ElementID)
if err != nil {
DebugLogf("Unable to find target user %d", elementID)
DebugLogf("Unable to find target user %d", alert.ElementID)
return "", errors.New(phrases.GetErrorPhrase("alerts_no_target_user"))
}
area = targetUser.Name
url = targetUser.Link
if targetUserID == user.ID {
if alert.TargetUserID == user.ID {
phraseName += "_own"
}
case "post":
topic, err := TopicByReplyID(elementID)
topic, err := TopicByReplyID(alert.ElementID)
if err != nil {
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic_by_reply"))
}
url = topic.Link
area = topic.Title
if targetUserID == user.ID {
if alert.TargetUserID == user.ID {
phraseName += "_own"
}
default:
return "", errors.New(phrases.GetErrorPhrase("alerts_invalid_elementtype"))
}
switch event {
switch alert.Event {
case "like":
phraseName += "_like"
case "mention":
@ -133,7 +145,7 @@ func BuildAlert(asid int, event string, elementType string, actorID int, targetU
phraseName += "_reply"
}
return buildAlertString(phrases.GetTmplPhrase(phraseName), []string{actor.Name, area}, url, actor.Avatar, asid), nil
return buildAlertString(phrases.GetTmplPhrase(phraseName), []string{alert.Actor.Name, area}, url, alert.Actor.Avatar, alert.ASID), nil
}
func buildAlertString(msg string, sub []string, path string, avatar string, asid int) string {
@ -160,22 +172,25 @@ func AddActivityAndNotifyAll(actor int, targetUser int, event string, elementTyp
return NotifyWatchers(lastID)
}
func AddActivityAndNotifyTarget(actor int, targetUser int, event string, elementType string, elementID int) error {
res, err := alertStmts.addActivity.Exec(actor, targetUser, event, elementType, elementID)
if err != nil {
return err
}
lastID, err := res.LastInsertId()
if err != nil {
return err
}
err = NotifyOne(targetUser, lastID)
func AddActivityAndNotifyTarget(alert Alert) error {
res, err := alertStmts.addActivity.Exec(alert.ActorID, alert.TargetUserID, alert.Event, alert.ElementType, alert.ElementID)
if err != nil {
return err
}
lastID, err := res.LastInsertId()
if err != nil {
return err
}
err = NotifyOne(alert.TargetUserID, lastID)
if err != nil {
return err
}
alert.ASID = int(lastID)
// Live alerts, if the target is online and WebSockets is enabled
_ = WsHub.pushAlert(targetUser, int(lastID), event, elementType, actor, targetUser, elementID)
_ = WsHub.pushAlert(alert.TargetUserID, alert)
return nil
}
@ -221,13 +236,12 @@ func notifyWatchers(asid int64) {
return
}
var actorID, targetUserID, elementID int
var event, elementType string
err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&actorID, &targetUserID, &event, &elementType, &elementID)
var alert = Alert{ASID: int(asid)}
err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID)
if err != nil && err != ErrNoRows {
LogError(err)
return
}
_ = WsHub.pushAlerts(uids, int(asid), event, elementType, actorID, targetUserID, elementID)
_ = WsHub.pushAlerts(uids, alert)
}

View File

@ -147,6 +147,13 @@ func (list SFileList) JSTmplInit() error {
data[braceAt] = ' ' // Blank this one too
}
})
each(" = []byte(", func(index int) {
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
if hasEndBrace {
data[braceAt] = ' ' // Blank it
}
})
each("w.Write(", func(index int) {
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
@ -185,6 +192,8 @@ func (list SFileList) JSTmplInit() error {
data = replace(data, shortName+"_tmpl_phrase_id = RegisterTmplPhraseNames([]string{", "[")
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let plist = tmplPhrases[\""+tmplName+"\"];")
//data = replace(data, "var phrases = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let phrases = tmplPhrases[\""+tmplName+"\"];\nconsole.log('tmplName:','"+tmplName+"')\nconsole.log('phrases:', phrases);")
data = replace(data, "var cached_var_", "let cached_var_")
data = replace(data, " = []byte(", " = ")
data = replace(data, "if ", "if(")
data = replace(data, "return nil", "return out")
data = replace(data, " )", ")")

View File

@ -96,9 +96,9 @@ func HandleExpiredScheduledGroups() error {
// TODO: Does this even work?
func HandleServerSync() error {
// We don't want to run any unnecessary queries when there is nothing to synchronise
/*if Config.ServerCount > 1 {
if Config.ServerCount == 1 {
return nil
}*/
}
var lastUpdate time.Time
err := taskStmts.getSync.QueryRow().Scan(&lastUpdate)

View File

@ -4,6 +4,11 @@ import (
"reflect"
)
// For use in generated code
type FragLite struct {
Body string
}
type Fragment struct {
Body string
TemplateName string
@ -23,6 +28,7 @@ type CContext struct {
VarHolder string
HoldReflect reflect.Value
TemplateName string
LoopDepth int
OutBuf *[]OutBufferFrame
}
@ -35,3 +41,36 @@ func (con *CContext) PushText(body string, fragIndex int, fragOutIndex int) (ind
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "text", con.TemplateName, fragIndex, fragOutIndex})
return len(*con.OutBuf) - 1
}
func (con *CContext) PushPhrase(body string, langIndex int) (index int) {
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "lang", con.TemplateName, langIndex, nil})
return len(*con.OutBuf) - 1
}
func (con *CContext) StartLoop(body string) (index int) {
con.LoopDepth++
return con.Push("startloop", body)
}
func (con *CContext) EndLoop(body string) (index int) {
return con.Push("endloop", body)
}
func (con *CContext) StartTemplate(body string) (index int) {
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "starttemplate", con.TemplateName, nil, nil})
return len(*con.OutBuf) - 1
}
func (con *CContext) EndTemplate(body string) (index int) {
return con.Push("endtemplate", body)
}
func (con *CContext) AttachVars(vars string, index int) {
outBuf := *con.OutBuf
node := outBuf[index]
if node.Type != "starttemplate" && node.Type != "startloop" {
panic("not a starttemplate node")
}
node.Body += vars
outBuf[index] = node
}

View File

@ -189,7 +189,10 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe
c.TemplateFragmentCount = make(map[string]int)
}
startIndex := con.StartTemplate("")
c.rootIterate(c.templateList[fname], con)
con.EndTemplate("")
c.afterTemplate(con, startIndex)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
_, ok := c.FragOnce[fname]
@ -280,6 +283,9 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe
skipBlock.Frags[frame.Extra.(int)] = skipBlock.LastCount
}
writeTextFrame(frame.TemplateName, frame.Extra.(int)-skip)
} else if frame.Type == "varsub" || frame.Type == "cvarsub" {
c.detail(frame.Type + " frame")
fout += "w.Write(" + frame.Body + ")\n"
} else {
c.detail(frame.Type + " frame")
fout += frame.Body
@ -425,12 +431,24 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
var startIf = func(item reflect.Value, useCopy bool) {
con.Push("startif", "if len("+expr+") != 0 {\n")
con.Push("startloop", "for _, item := range "+expr+" {\n")
startIndex := con.StartLoop("for _, item := range " + expr + " {\n")
ccon := con
ccon.VarHolder = "item"
var depth string
if ccon.VarHolder == "item" {
depth = strings.TrimPrefix(ccon.VarHolder, "item")
if depth != "" {
idepth, err := strconv.Atoi(depth)
if err != nil {
panic(err)
}
depth = strconv.Itoa(idepth + 1)
}
}
ccon.VarHolder = "item" + depth
ccon.HoldReflect = item
c.compileSwitch(ccon, node.List)
con.Push("endloop", "}\n")
con.EndLoop("}\n")
c.afterTemplate(con, startIndex)
if node.ElseList != nil {
con.Push("endif", "}")
con.Push("startelse", " else {\n")
@ -440,7 +458,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
c.compileSwitch(ccon, node.ElseList)
con.Push("endelse", "}\n")
} else {
con.Push("endloop", "}\n")
con.Push("endif", "}\n")
}
}
@ -1098,10 +1116,10 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
base = "[]byte(strconv.Itoa(" + varname + "))"
case reflect.Bool:
con.Push("startif", "if "+varname+" {\n")
con.Push("varsub", "w.Write([]byte(\"true\"))")
con.Push("varsub", "[]byte(\"true\")")
con.Push("endif", "} ")
con.Push("startelse", "else {\n")
con.Push("varsub", "w.Write([]byte(\"false\"))")
con.Push("varsub", "[]byte(\"false\")")
con.Push("endelse", "}\n")
return
case reflect.String:
@ -1121,7 +1139,6 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
fmt.Println("Unknown Type:", val.Type().Name())
panic("-- I don't know what this variable's type is o.o\n")
}
base = "w.Write(" + base + ")\n"
c.detail("base: ", base)
if assLines == "" {
con.Push("varsub", base)
@ -1179,13 +1196,18 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
c.templateList[fname] = tree
subtree := c.templateList[fname]
c.detail("subtree.Root", subtree.Root)
c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect}
c.fragmentCursor[fname] = 0
con.Push("starttemplate", "{\n")
var startBit, endBit string
if con.LoopDepth != 0 {
startBit = "{\n"
endBit = "}\n"
}
con.StartTemplate(startBit)
c.rootIterate(subtree, con)
con.Push("endtemplate", "}\n")
con.EndTemplate(endBit)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
_, ok := c.FragOnce[fname]
@ -1194,6 +1216,75 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
}
}
func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
c.dumpCall("afterTemplate", con, startIndex)
defer c.retCall("afterTemplate")
var loopDepth = 0
var outBuf = *con.OutBuf
var varcounts = make(map[string]int)
var loopStart = startIndex
if outBuf[startIndex].Type == "startloop" && (len(outBuf) > startIndex+1) {
loopStart++
}
// Exclude varsubs within loops for now
for i := loopStart; i < len(outBuf); i++ {
item := outBuf[i]
c.detail("item:", item)
if item.Type == "startloop" {
loopDepth++
c.detail("loopDepth:", loopDepth)
} else if item.Type == "endloop" {
loopDepth--
c.detail("loopDepth:", loopDepth)
if loopDepth == -1 {
break
}
} else if item.Type == "varsub" && loopDepth == 0 {
count := varcounts[item.Body]
varcounts[item.Body] = count + 1
c.detail("count " + strconv.Itoa(count) + " for " + item.Body)
c.detail("loopDepth:", loopDepth)
}
}
var varstr string
var i int
var varmap = make(map[string]int)
for name, count := range varcounts {
if count > 1 {
varstr += "var cached_var_" + strconv.Itoa(i) + " = " + name + "\n"
varmap[name] = i
i++
}
}
// Exclude varsubs within loops for now
loopDepth = 0
for i := loopStart; i < len(outBuf); i++ {
item := outBuf[i]
if item.Type == "startloop" {
loopDepth++
} else if item.Type == "endloop" {
loopDepth--
if loopDepth == -1 {
break
}
} else if item.Type == "varsub" && loopDepth == 0 {
index, ok := varmap[item.Body]
if ok {
item.Body = "cached_var_" + strconv.Itoa(index)
item.Type = "cvarsub"
outBuf[i] = item
}
}
}
con.AttachVars(varstr, startIndex)
}
// TODO: Should we rethink the way the log methods work or their names?
func (c *CTemplateSet) detail(args ...interface{}) {

View File

@ -345,17 +345,18 @@ func TopicByReplyID(rid int) (*Topic, error) {
// TODO: Refactor the caller to take a Topic and a User rather than a combined TopicUser
// TODO: Load LastReplyAt everywhere in here?
func GetTopicUser(tid int) (TopicUser, error) {
func GetTopicUser(user *User, tid int) (tu TopicUser, err error) {
tcache := Topics.GetCache()
ucache := Users.GetCache()
if tcache != nil && ucache != nil {
topic, err := tcache.Get(tid)
if err == nil {
user, err := Users.Get(topic.CreatedBy)
if err != nil {
return TopicUser{ID: tid}, err
if topic.CreatedBy != user.ID {
user, err = Users.Get(topic.CreatedBy)
if err != nil {
return TopicUser{ID: tid}, err
}
}
// We might be better off just passing separate topic and user structs to the caller?
return copyTopicToTopicUser(topic, user), nil
} else if ucache.Length() < ucache.GetCapacity() {
@ -363,16 +364,18 @@ func GetTopicUser(tid int) (TopicUser, error) {
if err != nil {
return TopicUser{ID: tid}, err
}
user, err := Users.Get(topic.CreatedBy)
if err != nil {
return TopicUser{ID: tid}, err
if topic.CreatedBy != user.ID {
user, err = Users.Get(topic.CreatedBy)
if err != nil {
return TopicUser{ID: tid}, err
}
}
return copyTopicToTopicUser(topic, user), nil
}
}
tu := TopicUser{ID: tid}
err := topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
tu = TopicUser{ID: tid}
err = topicStmts.getTopicUser.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.IsClosed, &tu.Sticky, &tu.ParentID, &tu.IPAddress, &tu.ViewCount, &tu.PostCount, &tu.LikeCount, &tu.Poll, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
tu.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy)

View File

@ -340,21 +340,19 @@ func (hub *WsHubImpl) PushMessage(targetUser int, msg string) error {
return wsUser.WriteAll(msg)
}
func (hub *WsHubImpl) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
func (hub *WsHubImpl) pushAlert(targetUser int, alert Alert) error {
wsUser, err := hub.getUser(targetUser)
if err != nil {
return err
}
alert, err := BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
astr, err := BuildAlert(alert, *wsUser.User)
if err != nil {
return err
}
return wsUser.WriteAll(alert)
return wsUser.WriteAll(astr)
}
func (hub *WsHubImpl) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
func (hub *WsHubImpl) pushAlerts(users []int, alert Alert) error {
wsUsers, err := hub.getUsers(users)
if err != nil {
return err
@ -365,8 +363,7 @@ func (hub *WsHubImpl) pushAlerts(users []int, asid int, event string, elementTyp
if wsUser == nil {
continue
}
alert, err := BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
alert, err := BuildAlert(alert, *wsUser.User)
if err != nil {
errs = append(errs, err)
}

12
main.go
View File

@ -377,7 +377,7 @@ func main() {
go tickLoop(thumbChan, halfSecondTicker, secondTicker, fifteenMinuteTicker, hourTicker)
// Resource Management Goroutine
/*go func() {
go func() {
ucache := common.Users.GetCache()
tcache := common.Topics.GetCache()
if ucache == nil && tcache == nil {
@ -393,17 +393,15 @@ func main() {
if ucache != nil {
ucap := ucache.GetCapacity()
if ucache.Length() <= ucap || common.Users.GlobalCount() <= ucap {
countNotDealloc = false
couldNotDealloc = false
continue
}
lastEvictedCount = ucache.DeallocOverflow(countNotDealloc)
countNotDealloc = (lastEvictedCount == 0)
} else {
countNotDealloc = false
lastEvictedCount = ucache.DeallocOverflow(couldNotDealloc)
couldNotDealloc = (lastEvictedCount == 0)
}
}
}
}()*/
}()
log.Print("Initialising the router")
router, err = NewGenRouter(http.FileServer(http.Dir("./uploads")))

View File

@ -1213,137 +1213,3 @@ func TestWordCount(t *testing.T) {
}
}
}
func TestPreparser(t *testing.T) {
var msgList = &METriList{nil}
// Note: The open tag is evaluated without knowledge of the close tag for efficiency and simplicity, so the parser autofills the associated close tag when it finds an open tag without a partner
msgList.Add("", "")
msgList.Add(" ", "")
msgList.Add(" hi", "hi")
msgList.Add("hi ", "hi")
msgList.Add("hi", "hi")
msgList.Add(":grinning:", "😀")
msgList.Add("😀", "😀")
msgList.Add("&nbsp;", "")
msgList.Add("<p>", "")
msgList.Add("</p>", "")
msgList.Add("<p></p>", "")
msgList.Add("<", "&lt;")
msgList.Add(">", "&gt;")
msgList.Add("<meow>", "&lt;meow&gt;")
msgList.Add("&lt;", "&amp;lt;")
msgList.Add("&", "&amp;")
// Note: strings.TrimSpace strips newlines, if there's nothing before or after them
msgList.Add("<br>", "")
msgList.Add("<br />", "")
msgList.Add("\\n", "\n", "")
msgList.Add("\\n\\n", "\n\n", "")
msgList.Add("\\n\\n\\n", "\n\n\n", "")
msgList.Add("\\r\\n", "\r\n", "") // Windows style line ending
msgList.Add("\\n\\r", "\n\r", "")
msgList.Add("ho<br>ho", "ho\n\nho")
msgList.Add("ho<br />ho", "ho\n\nho")
msgList.Add("ho\\nho", "ho\nho", "ho\nho")
msgList.Add("ho\\n\\nho", "ho\n\nho", "ho\n\nho")
//msgList.Add("ho\\n\\n\\n\\nho", "ho\n\n\n\nho", "ho\n\n\nho")
msgList.Add("ho\\r\\nho", "ho\r\nho", "ho\nho") // Windows style line ending
msgList.Add("ho\\n\\rho", "ho\n\rho", "ho\nho")
msgList.Add("<b></b>", "<strong></strong>")
msgList.Add("<b>hi</b>", "<strong>hi</strong>")
msgList.Add("<s>hi</s>", "<del>hi</del>")
msgList.Add("<del>hi</del>", "<del>hi</del>")
msgList.Add("<u>hi</u>", "<u>hi</u>")
msgList.Add("<em>hi</em>", "<em>hi</em>")
msgList.Add("<i>hi</i>", "<em>hi</em>")
msgList.Add("<strong>hi</strong>", "<strong>hi</strong>")
msgList.Add("<b><i>hi</i></b>", "<strong><em>hi</em></strong>")
msgList.Add("<strong><em>hi</em></strong>", "<strong><em>hi</em></strong>")
msgList.Add("<b><i><b>hi</b></i></b>", "<strong><em><strong>hi</strong></em></strong>")
msgList.Add("<strong><em><strong>hi</strong></em></strong>", "<strong><em><strong>hi</strong></em></strong>")
msgList.Add("<div>hi</div>", "&lt;div&gt;hi&lt;/div&gt;")
msgList.Add("<span>hi</span>", "hi") // This is stripped since the editor (Trumbowyg) likes blasting useless spans
msgList.Add("<span >hi</span>", "hi")
msgList.Add("<span style='background-color: yellow;'>hi</span>", "hi")
msgList.Add("<span style='background-color: yellow;'>>hi</span>", "&gt;hi")
msgList.Add("<b>hi", "<strong>hi</strong>")
msgList.Add("hi</b>", "hi&lt;/b&gt;")
msgList.Add("</b>", "&lt;/b&gt;")
msgList.Add("</del>", "&lt;/del&gt;")
msgList.Add("</strong>", "&lt;/strong&gt;")
msgList.Add("<b>", "<strong></strong>")
msgList.Add("<span style='background-color: yellow;'>hi", "hi")
msgList.Add("hi</span>", "hi")
msgList.Add("</span>", "")
msgList.Add("<span></span>", "")
msgList.Add("<span ></span>", "")
msgList.Add("<></>", "&lt;&gt;&lt;/&gt;")
msgList.Add("</><>", "&lt;/&gt;&lt;&gt;")
msgList.Add("<>", "&lt;&gt;")
msgList.Add("</>", "&lt;/&gt;")
msgList.Add("@", "@")
msgList.Add("@Admin", "@1")
msgList.Add("@Bah", "@Bah")
msgList.Add(" @Admin", "@1")
msgList.Add("\n@Admin", "@1")
msgList.Add("@Admin\n", "@1")
msgList.Add("@Admin\ndd", "@1\ndd")
msgList.Add("d@Admin", "d@Admin")
//msgList.Add("byte 0", string([]byte{0}), "")
msgList.Add("byte 'a'", string([]byte{'a'}), "a")
//msgList.Add("byte 255", string([]byte{255}), "")
//msgList.Add("rune 0", string([]rune{0}), "")
// TODO: Do a test with invalid UTF-8 input
for _, item := range msgList.Items {
res := common.PreparseMessage(item.Msg)
if res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
t.Error("Testing string '" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
//t.Error("Ouput in bytes:", []byte(res))
t.Error("Expected:", "'"+item.Expects+"'")
}
}
}
func TestParser(t *testing.T) {
var msgList = &METriList{nil}
msgList.Add("//github.com/Azareal/Gosora", "<a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a>")
msgList.Add("https://github.com/Azareal/Gosora", "<a href='https://github.com/Azareal/Gosora'>https://github.com/Azareal/Gosora</a>")
msgList.Add("http://github.com/Azareal/Gosora", "<a href='http://github.com/Azareal/Gosora'>http://github.com/Azareal/Gosora</a>")
msgList.Add("//github.com/Azareal/Gosora\n", "<a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a><br>")
msgList.Add("\n//github.com/Azareal/Gosora", "<br><a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a>")
msgList.Add("\n//github.com/Azareal/Gosora\n", "<br><a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a><br>")
msgList.Add("//github.com/Azareal/Gosora\n//github.com/Azareal/Gosora", "<a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a><br><a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a>")
msgList.Add("//github.com/Azareal/Gosora\n\n//github.com/Azareal/Gosora", "<a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a><br><br><a href='//github.com/Azareal/Gosora'>//github.com/Azareal/Gosora</a>")
msgList.Add("//"+common.Site.URL, "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a>")
msgList.Add("//"+common.Site.URL+"\n", "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a><br>")
msgList.Add("//"+common.Site.URL+"\n//"+common.Site.URL, "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a><br><a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a>")
msgList.Add("#tid-1", "<a href='/topic/1'>#tid-1</a>")
msgList.Add("https://github.com/Azareal/Gosora/#tid-1", "<a href='https://github.com/Azareal/Gosora/#tid-1'>https://github.com/Azareal/Gosora/#tid-1</a>")
msgList.Add("#fid-1", "<a href='/forum/1'>#fid-1</a>")
msgList.Add("@1", "<a href='/user/admin.1' class='mention'>@Admin</a>")
msgList.Add("@0", "<span style='color: red;'>[Invalid Profile]</span>")
msgList.Add("@-1", "<span style='color: red;'>[Invalid Profile]</span>1")
for _, item := range msgList.Items {
res := common.ParseMessage(item.Msg, 1, "forums")
if res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
t.Error("Testing string '" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:", "'"+item.Expects+"'")
}
}
}

142
parser_test.go Normal file
View File

@ -0,0 +1,142 @@
package main
import (
"testing"
"github.com/Azareal/Gosora/common"
)
func TestPreparser(t *testing.T) {
var msgList = &METriList{nil}
// Note: The open tag is evaluated without knowledge of the close tag for efficiency and simplicity, so the parser autofills the associated close tag when it finds an open tag without a partner
msgList.Add("", "")
msgList.Add(" ", "")
msgList.Add(" hi", "hi")
msgList.Add("hi ", "hi")
msgList.Add("hi", "hi")
msgList.Add(":grinning:", "😀")
msgList.Add("😀", "😀")
msgList.Add("&nbsp;", "")
msgList.Add("<p>", "")
msgList.Add("</p>", "")
msgList.Add("<p></p>", "")
msgList.Add("<", "&lt;")
msgList.Add(">", "&gt;")
msgList.Add("<meow>", "&lt;meow&gt;")
msgList.Add("&lt;", "&amp;lt;")
msgList.Add("&", "&amp;")
// Note: strings.TrimSpace strips newlines, if there's nothing before or after them
msgList.Add("<br>", "")
msgList.Add("<br />", "")
msgList.Add("\\n", "\n", "")
msgList.Add("\\n\\n", "\n\n", "")
msgList.Add("\\n\\n\\n", "\n\n\n", "")
msgList.Add("\\r\\n", "\r\n", "") // Windows style line ending
msgList.Add("\\n\\r", "\n\r", "")
msgList.Add("ho<br>ho", "ho\n\nho")
msgList.Add("ho<br />ho", "ho\n\nho")
msgList.Add("ho\\nho", "ho\nho", "ho\nho")
msgList.Add("ho\\n\\nho", "ho\n\nho", "ho\n\nho")
//msgList.Add("ho\\n\\n\\n\\nho", "ho\n\n\n\nho", "ho\n\n\nho")
msgList.Add("ho\\r\\nho", "ho\r\nho", "ho\nho") // Windows style line ending
msgList.Add("ho\\n\\rho", "ho\n\rho", "ho\nho")
msgList.Add("<b></b>", "<strong></strong>")
msgList.Add("<b>hi</b>", "<strong>hi</strong>")
msgList.Add("<s>hi</s>", "<del>hi</del>")
msgList.Add("<del>hi</del>", "<del>hi</del>")
msgList.Add("<u>hi</u>", "<u>hi</u>")
msgList.Add("<em>hi</em>", "<em>hi</em>")
msgList.Add("<i>hi</i>", "<em>hi</em>")
msgList.Add("<strong>hi</strong>", "<strong>hi</strong>")
msgList.Add("<b><i>hi</i></b>", "<strong><em>hi</em></strong>")
msgList.Add("<strong><em>hi</em></strong>", "<strong><em>hi</em></strong>")
msgList.Add("<b><i><b>hi</b></i></b>", "<strong><em><strong>hi</strong></em></strong>")
msgList.Add("<strong><em><strong>hi</strong></em></strong>", "<strong><em><strong>hi</strong></em></strong>")
msgList.Add("<div>hi</div>", "&lt;div&gt;hi&lt;/div&gt;")
msgList.Add("<span>hi</span>", "hi") // This is stripped since the editor (Trumbowyg) likes blasting useless spans
msgList.Add("<span >hi</span>", "hi")
msgList.Add("<span style='background-color: yellow;'>hi</span>", "hi")
msgList.Add("<span style='background-color: yellow;'>>hi</span>", "&gt;hi")
msgList.Add("<b>hi", "<strong>hi</strong>")
msgList.Add("hi</b>", "hi&lt;/b&gt;")
msgList.Add("</b>", "&lt;/b&gt;")
msgList.Add("</del>", "&lt;/del&gt;")
msgList.Add("</strong>", "&lt;/strong&gt;")
msgList.Add("<b>", "<strong></strong>")
msgList.Add("<span style='background-color: yellow;'>hi", "hi")
msgList.Add("hi</span>", "hi")
msgList.Add("</span>", "")
msgList.Add("<span></span>", "")
msgList.Add("<span ></span>", "")
msgList.Add("<></>", "&lt;&gt;&lt;/&gt;")
msgList.Add("</><>", "&lt;/&gt;&lt;&gt;")
msgList.Add("<>", "&lt;&gt;")
msgList.Add("</>", "&lt;/&gt;")
msgList.Add("@", "@")
msgList.Add("@Admin", "@1")
msgList.Add("@Bah", "@Bah")
msgList.Add(" @Admin", "@1")
msgList.Add("\n@Admin", "@1")
msgList.Add("@Admin\n", "@1")
msgList.Add("@Admin\ndd", "@1\ndd")
msgList.Add("d@Admin", "d@Admin")
//msgList.Add("byte 0", string([]byte{0}), "")
msgList.Add("byte 'a'", string([]byte{'a'}), "a")
//msgList.Add("byte 255", string([]byte{255}), "")
//msgList.Add("rune 0", string([]rune{0}), "")
// TODO: Do a test with invalid UTF-8 input
for _, item := range msgList.Items {
res := common.PreparseMessage(item.Msg)
if res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
t.Error("Testing string '" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
//t.Error("Ouput in bytes:", []byte(res))
t.Error("Expected:", "'"+item.Expects+"'")
}
}
}
func TestParser(t *testing.T) {
var msgList = &METriList{nil}
url := "github.com/Azareal/Gosora"
msgList.Add("//"+url, "<a href='//"+url+"'>//"+url+"</a>")
msgList.Add("https://"+url, "<a href='https://"+url+"'>https://"+url+"</a>")
msgList.Add("http://"+url, "<a href='http://"+url+"'>http://"+url+"</a>")
msgList.Add("//"+url+"\n", "<a href='//"+url+"'>//"+url+"</a><br>")
msgList.Add("\n//"+url, "<br><a href='//"+url+"'>//"+url+"</a>")
msgList.Add("\n//"+url+"\n", "<br><a href='//"+url+"'>//"+url+"</a><br>")
msgList.Add("//"+url+"\n//"+url, "<a href='//"+url+"'>//"+url+"</a><br><a href='//"+url+"'>//"+url+"</a>")
msgList.Add("//"+url+"\n\n//"+url, "<a href='//"+url+"'>//"+url+"</a><br><br><a href='//"+url+"'>//"+url+"</a>")
msgList.Add("//"+common.Site.URL, "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a>")
msgList.Add("//"+common.Site.URL+"\n", "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a><br>")
msgList.Add("//"+common.Site.URL+"\n//"+common.Site.URL, "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a><br><a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a>")
msgList.Add("#tid-1", "<a href='/topic/1'>#tid-1</a>")
msgList.Add("https://"+url+"/#tid-1", "<a href='https://"+url+"/#tid-1'>https://"+url+"/#tid-1</a>")
msgList.Add("#fid-1", "<a href='/forum/1'>#fid-1</a>")
msgList.Add("@1", "<a href='/user/admin.1' class='mention'>@Admin</a>")
msgList.Add("@0", "<span style='color: red;'>[Invalid Profile]</span>")
msgList.Add("@-1", "<span style='color: red;'>[Invalid Profile]</span>1")
for _, item := range msgList.Items {
res := common.ParseMessage(item.Msg, 1, "forums")
if res != item.Expects {
if item.Name != "" {
t.Error("Name: ", item.Name)
}
t.Error("Testing string '" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
t.Error("Expected:", "'"+item.Expects+"'")
}
}
}

View File

@ -10,6 +10,7 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
@ -65,10 +66,8 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
return nil
}
var msglist, event, elementType string
var asid, actorID, targetUserID, elementID int
var msglist string
var msgCount int
err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&msgCount)
if err == ErrNoRows {
return common.PreErrorJS("Couldn't find the parent topic", w, r)
@ -82,22 +81,43 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
}
defer rows.Close()
var actors []int
var alerts []common.Alert
for rows.Next() {
err = rows.Scan(&asid, &actorID, &targetUserID, &event, &elementType, &elementID)
var alert common.Alert
err = rows.Scan(&alert.ASID, &alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID)
if err != nil {
return common.InternalErrorJS(err, w, r)
}
res, err := common.BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, user)
if err != nil {
return common.LocalErrorJS(err.Error(), w, r)
}
msglist += res + ","
alerts = append(alerts, alert)
actors = append(actors, alert.ActorID)
}
err = rows.Err()
if err != nil {
return common.InternalErrorJS(err, w, r)
}
// Might not want to error here, if the account was deleted properly, we might want to figure out how we should handle deletions in general
list, err := common.Users.BulkGetMap(actors)
if err != nil {
return common.InternalErrorJS(err, w, r)
}
var ok bool
for _, alert := range alerts {
alert.Actor, ok = list[alert.ActorID]
if !ok {
return common.InternalErrorJS(errors.New("No such actor"), w, r)
}
res, err := common.BuildAlert(alert, user)
if err != nil {
return common.LocalErrorJS(err.Error(), w, r)
}
msglist += res + ","
}
if len(msglist) != 0 {
msglist = msglist[0 : len(msglist)-1]
}

View File

@ -328,7 +328,9 @@ func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user commo
return common.InternalError(err, w, r)
}
err = common.AddActivityAndNotifyTarget(user.ID, profileOwner.ID, "reply", "user", profileOwner.ID)
// ! Be careful about leaking per-route permission state with &user
alert := common.Alert{0, user.ID, profileOwner.ID, "reply", "user", profileOwner.ID, &user}
err = common.AddActivityAndNotifyTarget(alert)
if err != nil {
return common.InternalError(err, w, r)
}
@ -462,7 +464,9 @@ func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User, s
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.AddActivityAndNotifyTarget(user.ID, reply.CreatedBy, "like", "post", rid)
// ! Be careful about leaking per-route permission state with &user
alert := common.Alert{0, user.ID, reply.CreatedBy, "like", "post", rid, &user}
err = common.AddActivityAndNotifyTarget(alert)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}

View File

@ -45,7 +45,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
}
// Get the topic...
topic, err := common.GetTopicUser(tid)
topic, err := common.GetTopicUser(&user, tid)
if err == sql.ErrNoRows {
return common.NotFound(w, r, nil) // TODO: Can we add a simplified invocation of headerVars here? This is likely to be an extremely common NotFound
} else if err != nil {
@ -822,7 +822,9 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, s
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.AddActivityAndNotifyTarget(user.ID, topic.CreatedBy, "like", "topic", tid)
// ! Be careful about leaking per-route permission state with &user
alert := common.Alert{0, user.ID, topic.CreatedBy, "like", "topic", tid, &user}
err = common.AddActivityAndNotifyTarget(alert)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}