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" "strconv"
"strings" "strings"
"github.com/Azareal/Gosora/query_gen"
"github.com/Azareal/Gosora/common/phrases" "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 { type AlertStmts struct {
addActivity *sql.Stmt addActivity *sql.Stmt
notifyWatchers *sql.Stmt notifyWatchers *sql.Stmt
@ -50,81 +61,82 @@ func escapeTextInJson(in string) string {
return strings.Replace(in, "/", "\\/", -1) 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 var targetUser *User
if alert.Actor == nil {
actor, err := Users.Get(actorID) alert.Actor, err = Users.Get(alert.ActorID)
if err != nil { if err != nil {
return "", errors.New(phrases.GetErrorPhrase("alerts_no_actor")) return "", errors.New(phrases.GetErrorPhrase("alerts_no_actor"))
}
} }
/*if elementType != "forum" { /*if alert.ElementType != "forum" {
targetUser, err = users.Get(targetUserID) targetUser, err = users.Get(alert.TargetUserID)
if err != nil { if err != nil {
LocalErrorJS("Unable to find the target user",w,r) LocalErrorJS("Unable to find the target user",w,r)
return return
} }
}*/ }*/
if event == "friend_invite" { if alert.Event == "friend_invite" {
return buildAlertString(phrases.GetTmplPhrase("alerts.new_friend_invite"), []string{actor.Name}, actor.Link, actor.Avatar, asid), nil 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 // Not that many events for us to handle in a forum
if elementType == "forum" { if alert.ElementType == "forum" {
if event == "reply" { if alert.Event == "reply" {
topic, err := Topics.Get(elementID) topic, err := Topics.Get(alert.ElementID)
if err != nil { 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")) 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 // 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... // 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 url, area string
var phraseName = "alerts." + elementType var phraseName = "alerts." + alert.ElementType
switch elementType { switch alert.ElementType {
case "topic": case "topic":
topic, err := Topics.Get(elementID) topic, err := Topics.Get(alert.ElementID)
if err != nil { 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")) return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic"))
} }
url = topic.Link url = topic.Link
area = topic.Title area = topic.Title
if targetUserID == user.ID { if alert.TargetUserID == user.ID {
phraseName += "_own" phraseName += "_own"
} }
case "user": case "user":
targetUser, err = Users.Get(elementID) targetUser, err = Users.Get(alert.ElementID)
if err != nil { 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")) return "", errors.New(phrases.GetErrorPhrase("alerts_no_target_user"))
} }
area = targetUser.Name area = targetUser.Name
url = targetUser.Link url = targetUser.Link
if targetUserID == user.ID { if alert.TargetUserID == user.ID {
phraseName += "_own" phraseName += "_own"
} }
case "post": case "post":
topic, err := TopicByReplyID(elementID) topic, err := TopicByReplyID(alert.ElementID)
if err != nil { if err != nil {
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic_by_reply")) return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic_by_reply"))
} }
url = topic.Link url = topic.Link
area = topic.Title area = topic.Title
if targetUserID == user.ID { if alert.TargetUserID == user.ID {
phraseName += "_own" phraseName += "_own"
} }
default: default:
return "", errors.New(phrases.GetErrorPhrase("alerts_invalid_elementtype")) return "", errors.New(phrases.GetErrorPhrase("alerts_invalid_elementtype"))
} }
switch event { switch alert.Event {
case "like": case "like":
phraseName += "_like" phraseName += "_like"
case "mention": case "mention":
@ -133,7 +145,7 @@ func BuildAlert(asid int, event string, elementType string, actorID int, targetU
phraseName += "_reply" 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 { 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) return NotifyWatchers(lastID)
} }
func AddActivityAndNotifyTarget(actor int, targetUser int, event string, elementType string, elementID int) error { func AddActivityAndNotifyTarget(alert Alert) error {
res, err := alertStmts.addActivity.Exec(actor, targetUser, event, elementType, elementID) 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(targetUser, lastID)
if err != nil { if err != nil {
return err 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 // 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 return nil
} }
@ -221,13 +236,12 @@ func notifyWatchers(asid int64) {
return return
} }
var actorID, targetUserID, elementID int var alert = Alert{ASID: int(asid)}
var event, elementType string err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&alert.ActorID, &alert.TargetUserID, &alert.Event, &alert.ElementType, &alert.ElementID)
err = alertStmts.getActivityEntry.QueryRow(asid).Scan(&actorID, &targetUserID, &event, &elementType, &elementID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
LogError(err) LogError(err)
return 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 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) { each("w.Write(", func(index int) {
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')') braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace // 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, shortName+"_tmpl_phrase_id = RegisterTmplPhraseNames([]string{", "[")
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let plist = tmplPhrases[\""+tmplName+"\"];") 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 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, "if ", "if(")
data = replace(data, "return nil", "return out") data = replace(data, "return nil", "return out")
data = replace(data, " )", ")") data = replace(data, " )", ")")

View File

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

View File

@ -4,6 +4,11 @@ import (
"reflect" "reflect"
) )
// For use in generated code
type FragLite struct {
Body string
}
type Fragment struct { type Fragment struct {
Body string Body string
TemplateName string TemplateName string
@ -23,6 +28,7 @@ type CContext struct {
VarHolder string VarHolder string
HoldReflect reflect.Value HoldReflect reflect.Value
TemplateName string TemplateName string
LoopDepth int
OutBuf *[]OutBufferFrame 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}) *con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "text", con.TemplateName, fragIndex, fragOutIndex})
return len(*con.OutBuf) - 1 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) c.TemplateFragmentCount = make(map[string]int)
} }
startIndex := con.StartTemplate("")
c.rootIterate(c.templateList[fname], con) c.rootIterate(c.templateList[fname], con)
con.EndTemplate("")
c.afterTemplate(con, startIndex)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1 c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
_, ok := c.FragOnce[fname] _, 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 skipBlock.Frags[frame.Extra.(int)] = skipBlock.LastCount
} }
writeTextFrame(frame.TemplateName, frame.Extra.(int)-skip) 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 { } else {
c.detail(frame.Type + " frame") c.detail(frame.Type + " frame")
fout += frame.Body fout += frame.Body
@ -425,12 +431,24 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
var startIf = func(item reflect.Value, useCopy bool) { var startIf = func(item reflect.Value, useCopy bool) {
con.Push("startif", "if len("+expr+") != 0 {\n") 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 := 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 ccon.HoldReflect = item
c.compileSwitch(ccon, node.List) c.compileSwitch(ccon, node.List)
con.Push("endloop", "}\n") con.EndLoop("}\n")
c.afterTemplate(con, startIndex)
if node.ElseList != nil { if node.ElseList != nil {
con.Push("endif", "}") con.Push("endif", "}")
con.Push("startelse", " else {\n") con.Push("startelse", " else {\n")
@ -440,7 +458,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
c.compileSwitch(ccon, node.ElseList) c.compileSwitch(ccon, node.ElseList)
con.Push("endelse", "}\n") con.Push("endelse", "}\n")
} else { } 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 + "))" base = "[]byte(strconv.Itoa(" + varname + "))"
case reflect.Bool: case reflect.Bool:
con.Push("startif", "if "+varname+" {\n") con.Push("startif", "if "+varname+" {\n")
con.Push("varsub", "w.Write([]byte(\"true\"))") con.Push("varsub", "[]byte(\"true\")")
con.Push("endif", "} ") con.Push("endif", "} ")
con.Push("startelse", "else {\n") con.Push("startelse", "else {\n")
con.Push("varsub", "w.Write([]byte(\"false\"))") con.Push("varsub", "[]byte(\"false\")")
con.Push("endelse", "}\n") con.Push("endelse", "}\n")
return return
case reflect.String: 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()) fmt.Println("Unknown Type:", val.Type().Name())
panic("-- I don't know what this variable's type is o.o\n") panic("-- I don't know what this variable's type is o.o\n")
} }
base = "w.Write(" + base + ")\n"
c.detail("base: ", base) c.detail("base: ", base)
if assLines == "" { if assLines == "" {
con.Push("varsub", base) con.Push("varsub", base)
@ -1179,13 +1196,18 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
c.templateList[fname] = tree c.templateList[fname] = tree
subtree := c.templateList[fname] subtree := c.templateList[fname]
c.detail("subtree.Root", subtree.Root) c.detail("subtree.Root", subtree.Root)
c.localVars[fname] = make(map[string]VarItemReflect) c.localVars[fname] = make(map[string]VarItemReflect)
c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect} c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect}
c.fragmentCursor[fname] = 0 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) c.rootIterate(subtree, con)
con.Push("endtemplate", "}\n") con.EndTemplate(endBit)
c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1 c.TemplateFragmentCount[fname] = c.fragmentCursor[fname] + 1
_, ok := c.FragOnce[fname] _, 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? // TODO: Should we rethink the way the log methods work or their names?
func (c *CTemplateSet) detail(args ...interface{}) { 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: Refactor the caller to take a Topic and a User rather than a combined TopicUser
// TODO: Load LastReplyAt everywhere in here? // 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() tcache := Topics.GetCache()
ucache := Users.GetCache() ucache := Users.GetCache()
if tcache != nil && ucache != nil { if tcache != nil && ucache != nil {
topic, err := tcache.Get(tid) topic, err := tcache.Get(tid)
if err == nil { if err == nil {
user, err := Users.Get(topic.CreatedBy) if topic.CreatedBy != user.ID {
if err != nil { user, err = Users.Get(topic.CreatedBy)
return TopicUser{ID: tid}, err if err != nil {
return TopicUser{ID: tid}, err
}
} }
// We might be better off just passing separate topic and user structs to the caller? // We might be better off just passing separate topic and user structs to the caller?
return copyTopicToTopicUser(topic, user), nil return copyTopicToTopicUser(topic, user), nil
} else if ucache.Length() < ucache.GetCapacity() { } else if ucache.Length() < ucache.GetCapacity() {
@ -363,16 +364,18 @@ func GetTopicUser(tid int) (TopicUser, error) {
if err != nil { if err != nil {
return TopicUser{ID: tid}, err return TopicUser{ID: tid}, err
} }
user, err := Users.Get(topic.CreatedBy) if topic.CreatedBy != user.ID {
if err != nil { user, err = Users.Get(topic.CreatedBy)
return TopicUser{ID: tid}, err if err != nil {
return TopicUser{ID: tid}, err
}
} }
return copyTopicToTopicUser(topic, user), nil return copyTopicToTopicUser(topic, user), nil
} }
} }
tu := TopicUser{ID: tid} 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) 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.Avatar, tu.MicroAvatar = BuildAvatar(tu.CreatedBy, tu.Avatar)
tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID) tu.Link = BuildTopicURL(NameToSlug(tu.Title), tu.ID)
tu.UserLink = BuildProfileURL(NameToSlug(tu.CreatedByName), tu.CreatedBy) 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) 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) wsUser, err := hub.getUser(targetUser)
if err != nil { if err != nil {
return err return err
} }
astr, err := BuildAlert(alert, *wsUser.User)
alert, err := BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
if err != nil { if err != nil {
return err return err
} }
return wsUser.WriteAll(astr)
return wsUser.WriteAll(alert)
} }
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) wsUsers, err := hub.getUsers(users)
if err != nil { if err != nil {
return err return err
@ -365,8 +363,7 @@ func (hub *WsHubImpl) pushAlerts(users []int, asid int, event string, elementTyp
if wsUser == nil { if wsUser == nil {
continue continue
} }
alert, err := BuildAlert(alert, *wsUser.User)
alert, err := BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, *wsUser.User)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }

12
main.go
View File

@ -377,7 +377,7 @@ func main() {
go tickLoop(thumbChan, halfSecondTicker, secondTicker, fifteenMinuteTicker, hourTicker) go tickLoop(thumbChan, halfSecondTicker, secondTicker, fifteenMinuteTicker, hourTicker)
// Resource Management Goroutine // Resource Management Goroutine
/*go func() { go func() {
ucache := common.Users.GetCache() ucache := common.Users.GetCache()
tcache := common.Topics.GetCache() tcache := common.Topics.GetCache()
if ucache == nil && tcache == nil { if ucache == nil && tcache == nil {
@ -393,17 +393,15 @@ func main() {
if ucache != nil { if ucache != nil {
ucap := ucache.GetCapacity() ucap := ucache.GetCapacity()
if ucache.Length() <= ucap || common.Users.GlobalCount() <= ucap { if ucache.Length() <= ucap || common.Users.GlobalCount() <= ucap {
countNotDealloc = false couldNotDealloc = false
continue continue
} }
lastEvictedCount = ucache.DeallocOverflow(countNotDealloc) lastEvictedCount = ucache.DeallocOverflow(couldNotDealloc)
countNotDealloc = (lastEvictedCount == 0) couldNotDealloc = (lastEvictedCount == 0)
} else {
countNotDealloc = false
} }
} }
} }
}()*/ }()
log.Print("Initialising the router") log.Print("Initialising the router")
router, err = NewGenRouter(http.FileServer(http.Dir("./uploads"))) 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" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -65,10 +66,8 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
return nil return nil
} }
var msglist, event, elementType string var msglist string
var asid, actorID, targetUserID, elementID int
var msgCount int var msgCount int
err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&msgCount) err = stmts.getActivityCountByWatcher.QueryRow(user.ID).Scan(&msgCount)
if err == ErrNoRows { if err == ErrNoRows {
return common.PreErrorJS("Couldn't find the parent topic", w, r) 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() defer rows.Close()
var actors []int
var alerts []common.Alert
for rows.Next() { 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 { if err != nil {
return common.InternalErrorJS(err, w, r) return common.InternalErrorJS(err, w, r)
} }
res, err := common.BuildAlert(asid, event, elementType, actorID, targetUserID, elementID, user) alerts = append(alerts, alert)
if err != nil { actors = append(actors, alert.ActorID)
return common.LocalErrorJS(err.Error(), w, r)
}
msglist += res + ","
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
return common.InternalErrorJS(err, w, r) 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 { if len(msglist) != 0 {
msglist = msglist[0 : len(msglist)-1] 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) 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 { if err != nil {
return common.InternalError(err, w, r) 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) 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 { if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs) 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... // Get the topic...
topic, err := common.GetTopicUser(tid) topic, err := common.GetTopicUser(&user, tid)
if err == sql.ErrNoRows { 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 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 { } 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) 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 { if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs) return common.InternalErrorJSQ(err, w, r, isJs)
} }