Add TopicListIntTest interface.

Use inqbuild2 in DefaultTopicList.Tick.
Avoid doing a full paginator calculation in the topic list when the result is obvious.
Add defaultPagi and setForumList methods to DefaultTopicList.
Cache two more items in qcache.
Use a string builder in inqbuild and inqbuildstr.
Add the inqbuild2 function.
Reduce boilerplate in topic.go
This commit is contained in:
Azareal 2021-03-24 22:07:46 +10:00
parent 26e8bf32a7
commit 300defd460
3 changed files with 142 additions and 126 deletions

View File

@ -12,7 +12,7 @@ import (
"log"
"os"
"strconv"
//"sync"
"strings"
"sync/atomic"
"time"
@ -223,10 +223,10 @@ func eachall(stmt *sql.Stmt, f func(r *sql.Rows) error) error {
return rows.Err()
}
var qcache = []string{0: "?", 1: "?,?", 2: "?,?,?", 3: "?,?,?,?", 4: "?,?,?,?,?", 5: "?,?,?,?,?,?"}
var qcache = []string{0: "?", 1: "?,?", 2: "?,?,?", 3: "?,?,?,?", 4: "?,?,?,?,?", 5: "?,?,?,?,?,?", 6: "?,?,?,?,?,?,?", 7: "?,?,?,?,?,?,?,?"}
func inqbuild(ids []int) ([]interface{}, string) {
if len(ids) < 5 {
if len(ids) < 7 {
idList := make([]interface{}, len(ids))
for i, id := range ids {
idList[i] = strconv.Itoa(id)
@ -234,21 +234,38 @@ func inqbuild(ids []int) ([]interface{}, string) {
return idList, qcache[len(ids)-1]
}
var q string
var sb strings.Builder
sb.Grow((len(ids) * 2) - 1)
idList := make([]interface{}, len(ids))
for i, id := range ids {
idList[i] = strconv.Itoa(id)
if i == 0 {
q = "?"
sb.WriteRune('?')
} else {
q += ",?"
sb.WriteString(",?")
}
}
return idList, q
return idList, sb.String()
}
func inqbuild2(count int) string {
if count <= 7 {
return qcache[count-1]
}
var sb strings.Builder
sb.Grow((count * 2) - 1)
for i := 0; i <= count; i++ {
if i == 0 {
sb.WriteRune('?')
} else {
sb.WriteString(",?")
}
}
return sb.String()
}
func inqbuildstr(strs []string) ([]interface{}, string) {
if len(strs) < 5 {
if len(strs) < 7 {
idList := make([]interface{}, len(strs))
for i, id := range strs {
idList[i] = id
@ -256,15 +273,16 @@ func inqbuildstr(strs []string) ([]interface{}, string) {
return idList, qcache[len(strs)-1]
}
var q string
var sb strings.Builder
sb.Grow((len(strs) * 2) - 1)
idList := make([]interface{}, len(strs))
for i, id := range strs {
idList[i] = id
if i == 0 {
q = "?"
sb.WriteRune('?')
} else {
q += ",?"
sb.WriteString(",?")
}
}
return idList, q
return idList, sb.String()
}

View File

@ -28,6 +28,7 @@ type ForumTopicListHolder struct {
Paginator Paginator
}
// TODO: Should we return no rows errors on empty pages? Is this likely to break something?
type TopicListInt interface {
GetListByCanSee(canSee []int, page, orderby int, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, pagi Paginator, err error)
GetListByGroup(g *Group, page, orderby int, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, pagi Paginator, err error)
@ -35,6 +36,11 @@ type TopicListInt interface {
GetList(page, orderby int, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, pagi Paginator, err error)
}
type TopicListIntTest interface {
RawGetListByForum(f *Forum, page, orderby int) (topicList []*TopicsRow, pagi Paginator, err error)
Tick() error
}
type DefaultTopicList struct {
// TODO: Rewrite this to put permTree as the primary and put canSeeStr on each group?
oddGroups map[int][2]*TopicListHolder
@ -73,9 +79,7 @@ func NewDefaultTopicList(acc *qgen.Accumulator) (*DefaultTopicList, error) {
if err := acc.FirstError(); err != nil {
return nil, err
}
err := tList.Tick()
if err != nil {
if err := tList.Tick(); err != nil {
return nil, err
}
@ -181,13 +185,7 @@ func (tList *DefaultTopicList) Tick() error {
continue
}
var qlist string
for i := 0; i < top; i++ {
if i != 0 {
qlist += ","
}
qlist += "?"
}
qlist := inqbuild2(top - 1)
cols := "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data"
stmt, err := qgen.Builder.SimpleSelect("topics", cols, "parentID IN("+qlist+")", "views DESC,lastReplyAt DESC,createdBy DESC", "?,?")
@ -260,14 +258,9 @@ func (tList *DefaultTopicList) Tick() error {
// TODO: Avoid rebuilding the entire list on every tick
fList := make(map[int]*ForumTopicListHolder)
for _, f := range fshort {
topicList, pagi := []*TopicsRow{}, Paginator{}
if f.TopicCount == 0 {
page := 1
_, page, lastPage := PageOffset(f.TopicCount, page, Config.ItemsPerPage)
pageList := Paginate(page, lastPage, 5)
pagi = Paginator{pageList, page, lastPage}
} else {
topicList, pagi, err = tList.getListByForum(f, 1, 0)
topicList, pagi := []*TopicsRow{}, tList.defaultPagi()
if f.TopicCount != 0 {
topicList, pagi, err = tList.RawGetListByForum(f, 1, 0)
if err != nil {
return err
}
@ -281,9 +274,8 @@ func (tList *DefaultTopicList) Tick() error {
fList[f.ID] = &ForumTopicListHolder{topicList, pagi}*/
}
tList.forumLock.Lock()
tList.forums = fList
tList.forumLock.Unlock()
//fmt.Printf("fList: %+v\n", fList)
tList.setForumList(fList)
hTbl := GetHookTable()
_, _ = hTbl.VhookSkippable("tasks_tick_topic_list", tList)
@ -291,6 +283,19 @@ func (tList *DefaultTopicList) Tick() error {
return nil
}
func (tList *DefaultTopicList) defaultPagi() Paginator {
/*_, page, lastPage := PageOffset(f.TopicCount, page, Config.ItemsPerPage)
pageList := Paginate(page, lastPage, 5)
return topicList, Paginator{pageList, page, lastPage}, nil*/
return Paginator{[]int{}, 1, 1}
}
func (tList *DefaultTopicList) setForumList(forums map[int]*ForumTopicListHolder) {
tList.forumLock.Lock()
tList.forums = forums
tList.forumLock.Unlock()
}
/*var reloadForumMutex sync.Mutex
// TODO: Avoid firing this multiple times per sec tick
@ -313,13 +318,8 @@ func (tList *DefaultTopicList) ReloadForum(id int) error {
}
tList.forumLock.Unlock()
topicList, pagi := []*TopicsRow{}, Paginator{}
if forum.TopicCount == 0 {
page := 1
_, page, lastPage := PageOffset(forum.TopicCount, page, Config.ItemsPerPage)
pageList := Paginate(page, lastPage, 5)
pagi = Paginator{pageList, page, lastPage}
} else {
topicList, pagi := []*TopicsRow{}, tList.defaultPagi()
if forum.TopicCount != 0 {
topicList, pagi, err = tList.getListByForum(forum, 1, 0)
if err != nil {
return err
@ -327,9 +327,7 @@ func (tList *DefaultTopicList) ReloadForum(id int) error {
}
fList[forum.ID] = &ForumTopicListHolder{topicList, pagi}
tList.forumLock.Lock()
tList.forums = fList
tList.forumLock.Unlock()
tList.setForumList(fList)
return nil
}*/
@ -340,9 +338,7 @@ func (tList *DefaultTopicList) GetListByForum(f *Forum, page, orderby int) (topi
page = 1
}
if f.TopicCount == 0 {
_, page, lastPage := PageOffset(f.TopicCount, page, Config.ItemsPerPage)
pageList := Paginate(page, lastPage, 5)
return topicList, Paginator{pageList, page, lastPage}, nil
return topicList, tList.defaultPagi(), nil
}
if page == 1 && orderby == 0 {
var h *ForumTopicListHolder
@ -354,16 +350,16 @@ func (tList *DefaultTopicList) GetListByForum(f *Forum, page, orderby int) (topi
return h.List, h.Paginator, nil
}
}
return tList.getListByForum(f, page, orderby)
return tList.RawGetListByForum(f, page, orderby)
}
func (tList *DefaultTopicList) getListByForum(f *Forum, page, orderby int) (topicList []*TopicsRow, pagi Paginator, err error) {
func (tList *DefaultTopicList) RawGetListByForum(f *Forum, page, orderby int) (topicList []*TopicsRow, pagi Paginator, err error) {
// TODO: Does forum.TopicCount take the deleted items into consideration for guests? We don't have soft-delete yet, only hard-delete
offset, page, lastPage := PageOffset(f.TopicCount, page, Config.ItemsPerPage)
rows, err := tList.getTopicsByForum.Query(f.ID, offset, Config.ItemsPerPage)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
defer rows.Close()
@ -373,7 +369,7 @@ func (tList *DefaultTopicList) getListByForum(f *Forum, page, orderby int) (topi
t := TopicsRow{Topic: Topic{ParentID: f.ID}}
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IsClosed, &t.Sticky, &t.CreatedAt, &t.LastReplyAt, &t.LastReplyBy, &t.LastReplyID, &t.ViewCount, &t.PostCount, &t.LikeCount)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
@ -387,7 +383,7 @@ func (tList *DefaultTopicList) getListByForum(f *Forum, page, orderby int) (topi
reqUserList[t.LastReplyBy] = true
}
if err = rows.Err(); err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
// Convert the user ID map to a slice, then bulk load the users
@ -401,7 +397,7 @@ func (tList *DefaultTopicList) getListByForum(f *Forum, page, orderby int) (topi
// TODO: What if a user is deleted via the Control Panel?
userList, err := Users.BulkGetMap(idSlice)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
// Second pass to the add the user data
@ -411,6 +407,9 @@ func (tList *DefaultTopicList) getListByForum(f *Forum, page, orderby int) (topi
t.LastUser = userList[t.LastReplyBy]
}
if len(topicList) == 0 {
return topicList, tList.defaultPagi(), nil
}
pageList := Paginate(page, lastPage, 5)
return topicList, Paginator{pageList, page, lastPage}, nil
}
@ -489,7 +488,7 @@ func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page, orderby int,
argList, qlist := ForumListToArgQ(filteredForums)
if qlist == "" {
// We don't want to kill the page, so pass an empty slice and nil error
return topicList, filteredForums, Paginator{[]int{}, 1, 1}, nil
return topicList, filteredForums, tList.defaultPagi(), nil
}
topicList, pagi, err = tList.getList(page, orderby, topicCount, argList, qlist)
@ -501,7 +500,7 @@ func (tList *DefaultTopicList) GetList(page, orderby int, filterIDs []int) (topi
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
cCanSee, err := Forums.GetAllVisibleIDs()
if err != nil {
return nil, nil, Paginator{nil, 1, 1}, err
return nil, nil, tList.defaultPagi(), err
}
//log.Printf("cCanSee: %+v\n", cCanSee)
inSlice := func(haystack []int, needle int) bool {
@ -546,7 +545,7 @@ func (tList *DefaultTopicList) GetList(page, orderby int, filterIDs []int) (topi
argList, qlist := ForumListToArgQ(forumList)
if qlist == "" {
// If the super admin can't see anything, then things have gone terribly wrong
return topicList, forumList, Paginator{[]int{}, 1, 1}, err
return topicList, forumList, tList.defaultPagi(), err
}
topicList, pagi, err = tList.getList(page, orderby, topicCount, argList, qlist)
@ -557,7 +556,7 @@ func (tList *DefaultTopicList) GetList(page, orderby int, filterIDs []int) (topi
// TODO: Make orderby an enum of sorts
func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []interface{}, qlist string) (topicList []*TopicsRow, paginator Paginator, err error) {
if topicCount == 0 {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
//log.Printf("argList: %+v\n",argList)
//log.Printf("qlist: %+v\n",qlist)
@ -580,12 +579,12 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
}
topicCount, err = ArgQToWeekViewTopicCount(argList, qlist)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
acc := qgen.NewAcc()
stmt = acc.Select("topics").Columns(cols).Where("parentID IN(" + qlist + ") AND (weekEvenViews!=0 OR weekOddViews!=0)").Orderby(orderq).Limit("?,?").ComplexPrepare()
if e := acc.FirstError(); e != nil {
return nil, Paginator{nil, 1, 1}, e
return nil, tList.defaultPagi(), e
}
defer stmt.Close()
}
@ -612,7 +611,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
if stmt == nil {
stmt, err = qgen.Builder.SimpleSelect("topics", cols, "parentID IN("+qlist+")", orderq, "?,?")
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
defer stmt.Close()
}
@ -622,14 +621,13 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
rows, err := stmt.Query(argList...)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
defer rows.Close()
rc := Rstore.GetCache()
rc, tc := Rstore.GetCache(), Topics.GetCache()
rcap := rc.GetCapacity()
rlen := rc.Length()
tc := Topics.GetCache()
reqUserList := make(map[int]bool)
for rows.Next() {
// TODO: Embed Topic structs in TopicsRow to make it easier for us to reuse this work in the topic cache
@ -637,7 +635,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
//var weekViews []uint8
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IsClosed, &t.Sticky, &t.CreatedAt, &t.LastReplyAt, &t.LastReplyBy, &t.LastReplyID, &t.ParentID, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data, &t.WeekViews)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
//t.WeekViews = int(weekViews[0])
//log.Printf("t: %+v\n", t)
@ -675,7 +673,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
if t.PostCount == 2 && rlen < rcap && !hRids && page < 5 {
rids, err := GetRidsForTopic(t.ID, 0)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
//log.Print("rids: ", rids)
@ -695,7 +693,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
}
}
if err = rows.Err(); err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
// TODO: specialcase for when reqUserList only has one or two items to avoid map alloc
@ -704,7 +702,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
for uid, _ := range reqUserList {
u, err = Users.Get(uid)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
}
for _, t := range topicList {
@ -723,7 +721,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
// TODO: What if a user is deleted via the Control Panel?
userList, err := Users.BulkGetMap(idSlice)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
return nil, tList.defaultPagi(), err
}
// Second pass to the add the user data

View File

@ -8,7 +8,6 @@ import (
"errors"
"io"
//"fmt"
"image"
"image/gif"
"image/jpeg"
@ -24,7 +23,7 @@ import (
c "github.com/Azareal/Gosora/common"
co "github.com/Azareal/Gosora/common/counters"
"github.com/Azareal/Gosora/common/phrases"
p "github.com/Azareal/Gosora/common/phrases"
qgen "github.com/Azareal/Gosora/query_gen"
)
@ -48,7 +47,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header
page, _ := strconv.Atoi(r.FormValue("page"))
_, tid, err := ParseSEOURL(urlBit)
if err != nil {
return c.SimpleError(phrases.GetErrorPhrase("url_id_must_be_integer"), w, r, h)
return c.SimpleError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, h)
}
// Get the topic...
@ -158,6 +157,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header
} else if err != nil {
return c.InternalError(err, w, r)
}
//fmt.Printf("rlist: %+v\n",rlist)
tpage.ItemList = rlist
if externalHead {
h.ExternalMedia = true
@ -206,7 +206,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header
func AttachTopicActCommon(w http.ResponseWriter, r *http.Request, u *c.User, stid string) (t *c.Topic, ferr c.RouteError) {
tid, err := strconv.Atoi(stid)
if err != nil {
return t, c.LocalErrorJS(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
return t, c.LocalErrorJS(p.GetErrorPhrase("id_must_be_integer"), w, r)
}
t, err = c.Topics.Get(tid)
if err != nil {
@ -268,7 +268,7 @@ func RemoveAttachFromTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.Us
for _, said := range strings.Split(r.PostFormValue("aids"), ",") {
aid, err := strconv.Atoi(said)
if err != nil {
return c.LocalErrorJS(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
return c.LocalErrorJS(p.GetErrorPhrase("id_must_be_integer"), w, r)
}
rerr := deleteAttachment(w, r, u, aid, true)
if rerr != nil {
@ -293,7 +293,7 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header,
if sfid != "" {
fid, err = strconv.Atoi(sfid)
if err != nil {
return c.LocalError(phrases.GetErrorPhrase("url_id_must_be_integer"), w, r, u)
return c.LocalError(p.GetErrorPhrase("url_id_must_be_integer"), w, r, u)
}
}
if fid == 0 {
@ -308,7 +308,7 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header,
return c.NoPermissions(w, r, u)
}
// TODO: Add a phrase for this
h.Title = phrases.GetTitlePhrase("create_topic")
h.Title = p.GetTitlePhrase("create_topic")
h.Zone = "create_topic"
// Lock this to the forum being linked?
@ -361,7 +361,7 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header,
func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
fid, err := strconv.Atoi(r.PostFormValue("board"))
if err != nil {
return c.LocalError(phrases.GetErrorPhrase("id_must_be_integer"), w, r, u)
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, u)
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, u, fid)
@ -562,11 +562,11 @@ func uploadFilesWithHash(w http.ResponseWriter, r *http.Request, u *c.User, dir
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid string) c.RouteError {
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, stid string) c.RouteError {
js := (r.PostFormValue("js") == "1")
tid, err := strconv.Atoi(stid)
if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, js)
return c.PreErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, js)
}
topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows {
@ -576,15 +576,15 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.EditTopic {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ViewTopic || !u.Perms.EditTopic {
return c.NoPermissionsJSQ(w, r, u, js)
}
if topic.IsClosed && !user.Perms.CloseTopic {
return c.NoPermissionsJSQ(w, r, user, js)
if topic.IsClosed && !u.Perms.CloseTopic {
return c.NoPermissionsJSQ(w, r, u, js)
}
err = topic.Update(r.PostFormValue("name"), r.PostFormValue("content"))
@ -592,16 +592,16 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid
if err != nil {
switch err {
case c.ErrNoTitle:
return c.LocalErrorJSQ("This topic doesn't have a title", w, r, user, js)
return c.LocalErrorJSQ("This topic doesn't have a title", w, r, u, js)
case c.ErrLongTitle:
return c.LocalErrorJSQ("The length of the title is too long, max: "+strconv.Itoa(c.Config.MaxTopicTitleLength), w, r, user, js)
return c.LocalErrorJSQ("The length of the title is too long, max: "+strconv.Itoa(c.Config.MaxTopicTitleLength), w, r, u, js)
case c.ErrNoBody:
return c.LocalErrorJSQ("This topic doesn't have a body", w, r, user, js)
return c.LocalErrorJSQ("This topic doesn't have a body", w, r, u, js)
}
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.Forums.UpdateLastTopic(topic.ID, user.ID, topic.ParentID)
err = c.Forums.UpdateLastTopic(topic.ID, u.ID, topic.ParentID)
if err != nil && err != sql.ErrNoRows {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -614,7 +614,7 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid
return c.InternalErrorJSQ(err, w, r, js)
}
skip, rerr := lite.Hooks.VhookSkippable("action_end_edit_topic", topic.ID, user)
skip, rerr := lite.Hooks.VhookSkippable("action_end_edit_topic", topic.ID, u)
if skip || rerr != nil {
return rerr
}
@ -622,7 +622,7 @@ func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid
if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums", user.ParseSettings, user)})
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(topic.Content, topic.ParentID, "forums", u.ParseSettings, u)})
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -722,7 +722,7 @@ func StickTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, stid st
func topicActionPre(stid, action string, w http.ResponseWriter, r *http.Request, u *c.User) (*c.Topic, *c.HeaderLite, c.RouteError) {
tid, err := strconv.Atoi(stid)
if err != nil {
return nil, nil, c.PreError(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
return nil, nil, c.PreError(p.GetErrorPhrase("id_must_be_integer"), w, r)
}
t, err := c.Topics.Get(tid)
if err == sql.ErrNoRows {
@ -766,7 +766,7 @@ func UnstickTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, stid
return topicActionPost(t.Unstick(), "unstick", w, r, lite, t, u)
}
func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
func LockTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
// TODO: Move this to some sort of middleware
var tids []int
js := c.ReqIsJson(r)
@ -786,7 +786,7 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.Rou
tids = append(tids, tid)
}
if len(tids) == 0 {
return c.LocalErrorJSQ("You haven't provided any IDs", w, r, user, js)
return c.LocalErrorJSQ("You haven't provided any IDs", w, r, u, js)
}
for _, tid := range tids {
@ -798,12 +798,12 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.Rou
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CloseTopic {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ViewTopic || !u.Perms.CloseTopic {
return c.NoPermissionsJSQ(w, r, u, js)
}
err = topic.Lock()
@ -811,13 +811,13 @@ func LockTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.Rou
return c.InternalErrorJSQ(err, w, r, js)
}
err = addTopicAction("lock", topic, user)
err = addTopicAction("lock", topic, u)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
// TODO: Do a bulk lock action hook?
skip, rerr := lite.Hooks.VhookSkippable("action_end_lock_topic", topic.ID, user)
skip, rerr := lite.Hooks.VhookSkippable("action_end_lock_topic", topic.ID, u)
if skip || rerr != nil {
return rerr
}
@ -842,10 +842,10 @@ func UnlockTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, stid s
// ! JS only route
// TODO: Figure a way to get this route to work without JS
func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError {
func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
fid, err := strconv.Atoi(sfid)
if err != nil {
return c.PreErrorJS(phrases.GetErrorPhrase("id_must_be_integer"), w, r)
return c.PreErrorJS(p.GetErrorPhrase("id_must_be_integer"), w, r)
}
// TODO: Move this to some sort of middleware
var tids []int
@ -869,19 +869,19 @@ func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid
}
// TODO: Add hooks to make use of headerLite
_, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
_, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.MoveTopic {
return c.NoPermissionsJS(w, r, user)
if !u.Perms.ViewTopic || !u.Perms.MoveTopic {
return c.NoPermissionsJS(w, r, u)
}
lite, ferr := c.SimpleForumUserCheck(w, r, user, fid)
lite, ferr := c.SimpleForumUserCheck(w, r, u, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.MoveTopic {
return c.NoPermissionsJS(w, r, user)
if !u.Perms.ViewTopic || !u.Perms.MoveTopic {
return c.NoPermissionsJS(w, r, u)
}
err = topic.MoveTo(fid)
@ -889,13 +889,13 @@ func MoveTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid
return c.InternalErrorJS(err, w, r)
}
// ? - Is there a better way of doing this?
err = addTopicAction("move-"+strconv.Itoa(fid), topic, user)
err = addTopicAction("move-"+strconv.Itoa(fid), topic, u)
if err != nil {
return c.InternalErrorJS(err, w, r)
}
// TODO: Do a bulk move action hook?
skip, rerr := lite.Hooks.VhookSkippable("action_end_move_topic", topic.ID, user)
skip, rerr := lite.Hooks.VhookSkippable("action_end_move_topic", topic.ID, u)
if skip || rerr != nil {
return rerr
}
@ -916,11 +916,11 @@ func addTopicAction(action string, t *c.Topic, u *c.User) error {
}
// TODO: Refactor this
func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid string) c.RouteError {
func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, stid string) c.RouteError {
js := r.PostFormValue("js") == "1"
tid, err := strconv.Atoi(stid)
if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, js)
return c.PreErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, js)
}
topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows {
@ -930,50 +930,50 @@ func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ViewTopic || !u.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, u, js)
}
if topic.CreatedBy == user.ID {
return c.LocalErrorJSQ("You can't like your own topics", w, r, user, js)
if topic.CreatedBy == u.ID {
return c.LocalErrorJSQ("You can't like your own topics", w, r, u, js)
}
_, err = c.Users.Get(topic.CreatedBy)
if err != nil && err == sql.ErrNoRows {
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
return c.LocalErrorJSQ("The target user doesn't exist", w, r, u, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
score := 1
err = topic.Like(score, user.ID)
err = topic.Like(score, u.ID)
if err == c.ErrAlreadyLiked {
return c.LocalErrorJSQ("You already liked this", w, r, user, js)
return c.LocalErrorJSQ("You already liked this", w, r, u, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
// ! Be careful about leaking per-route permission state with user ptr
alert := c.Alert{ActorID: user.ID, TargetUserID: topic.CreatedBy, Event: "like", ElementType: "topic", ElementID: tid, Actor: user}
alert := c.Alert{ActorID: u.ID, TargetUserID: topic.CreatedBy, Event: "like", ElementType: "topic", ElementID: tid, Actor: u}
err = c.AddActivityAndNotifyTarget(alert)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, user)
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_topic", topic.ID, u)
if skip || rerr != nil {
return rerr
}
return actionSuccess(w, r, "/topic/"+strconv.Itoa(tid), js)
}
func UnlikeTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, stid string) c.RouteError {
func UnlikeTopicSubmit(w http.ResponseWriter, r *http.Request, u *c.User, stid string) c.RouteError {
js := r.PostFormValue("js") == "1"
tid, err := strconv.Atoi(stid)
if err != nil {
return c.PreErrorJSQ(phrases.GetErrorPhrase("id_must_be_integer"), w, r, js)
return c.PreErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, js)
}
topic, err := c.Topics.Get(tid)
if err == sql.ErrNoRows {
@ -983,21 +983,21 @@ func UnlikeTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sti
}
// TODO: Add hooks to make use of headerLite
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ViewTopic || !u.Perms.LikeItem {
return c.NoPermissionsJSQ(w, r, u, js)
}
_, err = c.Users.Get(topic.CreatedBy)
if err != nil && err == sql.ErrNoRows {
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
return c.LocalErrorJSQ("The target user doesn't exist", w, r, u, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = topic.Unlike(user.ID)
err = topic.Unlike(u.ID)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
@ -1015,7 +1015,7 @@ func UnlikeTopicSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sti
return c.InternalErrorJSQ(err, w, r, js)
}
skip, rerr := lite.Hooks.VhookSkippable("action_end_unlike_topic", topic.ID, user)
skip, rerr := lite.Hooks.VhookSkippable("action_end_unlike_topic", topic.ID, u)
if skip || rerr != nil {
return rerr
}