From c60118e7c43d381ebc533473bdfabb61862581ba Mon Sep 17 00:00:00 2001 From: Azareal Date: Thu, 8 Apr 2021 00:23:11 +1000 Subject: [PATCH] WIP forum action code. Currently disabled. Add Http Conn Count tracking. Move more panel phrases into the panel namespace. Use a string builder in hookgen. Use Countf() in a couple of places to eliminate boilerplate. Reduce prepared stmt boilerplate in forum store with a lambda. Reduce prepared stmt boilerplate in topic.go with a lambda. Reduce prepared stmt boilerplate in group.go with a lambda. Add TestSetCreatedAt method to *Topic. Add DateOlderThanQ method to *accDeleteBuilder and *accUpdateBuilder. Add Stmt method to *accUpdateBuilder and *AccSelectBuilder. Add AccBuilder interface. Shorten variable names. Shorten extractPerm name to ep. Add avatar_visibility setting stub. Implementation coming in a later commit. Don't set an IP for installer generated posts. Add counters_perf_tick_row hook. Add avatar_visibility phrase. Add avatar_visibility_label phrase. Rename forums_no_description to forums_no_desc. Rename panel.forums_create_description_label to panel.forums_create_desc_label. Rename panel.forums_create_description to panel.forums_create_desc. Rename panel_forum_description to panel.forum_desc. Rename panel_forum_description_placeholder to panel.forum_desc_placeholder. Add panel_debug_http_conns_label phrase. Add panel.forum_actions_head phrase. Add panel.forum_actions_create_head phrase. Add panel.forum_action_run_on_topic_creation phrase. Add panel.forum_action_run_days_after_topic_creation phrase. Add panel.forum_action_run_days_after_topic_last_reply phrase. Add panel.forum_action_action phrase. Add panel.forum_action_action_delete phrase. Add panel.forum_action_action_lock phrase. Add panel.forum_action_action_unlock phrase. Add panel.forum_action_action_move phrase. Add panel.forum_action_extra phrase. Add panel.forum_action_create_button phrase. You will need to run the patcher / updater for this commit. --- cmd/common_hook_gen/hookgen.go | 1 + cmd/hook_gen/main.go | 31 +- common/activity_stream.go | 22 +- common/common.go | 21 + common/conversations.go | 27 +- common/counters/performance.go | 17 +- common/extend.go | 2 + common/forum_actions.go | 399 +++++++++++ common/forum_perms_store.go | 20 +- common/forum_store.go | 17 +- common/group.go | 10 +- common/group_store.go | 38 +- common/pages.go | 21 +- common/relations.go | 45 +- common/tasks.go | 48 +- common/template_init.go | 20 +- common/topic.go | 288 ++++---- common/weak_passwords.go | 12 +- common/websockets.go | 41 +- common/widget.go | 14 +- common/ws_user.go | 22 +- gen_router.go | 922 +++++++++++++------------- install/install.go | 16 +- langs/english.json | 65 +- main.go | 14 +- misc_test.go | 116 +++- patcher/patches.go | 105 +-- query_gen/acc_builders.go | 39 +- router_gen/routes.go | 2 + routes/panel/debug.go | 33 +- routes/panel/forums.go | 172 ++++- routes/panel/word_filters.go | 18 +- routes/topic_list.go | 4 +- schema/mssql/inserts.sql | 5 +- schema/mysql/inserts.sql | 5 +- schema/mysql/query_forums_actions.sql | 10 + schema/pgsql/inserts.sql | 5 +- schema/pgsql/query_users.sql | 4 +- schema/pgsql/query_widgets.sql | 2 +- templates/forums.html | 2 +- templates/panel_debug.html | 6 +- templates/panel_forum_edit.html | 103 ++- templates/panel_forum_edit_perms.html | 4 +- templates/panel_forums.html | 18 +- 44 files changed, 1793 insertions(+), 993 deletions(-) create mode 100644 common/forum_actions.go create mode 100644 schema/mysql/query_forums_actions.sql diff --git a/cmd/common_hook_gen/hookgen.go b/cmd/common_hook_gen/hookgen.go index 21bf6214..7cf699d7 100644 --- a/cmd/common_hook_gen/hookgen.go +++ b/cmd/common_hook_gen/hookgen.go @@ -41,6 +41,7 @@ func AddHooks(add func(name, params, ret, htype string, multiHook, skip bool, de } vhooknoret("router_end", "w http.ResponseWriter,r *http.Request,u *User,prefix string,extraData string") vhooknoret("topic_reply_row_assign", "r *ReplyUser") + vhooknoret("counters_perf_tick_row", "low int64,high int64,avg int64") //forums_frow_assign //Hook(name string, data interface{}) interface{} /*hook := func(name, params, ret, pure string) { diff --git a/cmd/hook_gen/main.go b/cmd/hook_gen/main.go index a7c56bc5..ecc3c65a 100644 --- a/cmd/hook_gen/main.go +++ b/cmd/hook_gen/main.go @@ -5,12 +5,12 @@ package main // import "github.com/Azareal/Gosora/hook_gen" import ( "fmt" "log" - "strings" "runtime/debug" - - _ "github.com/Azareal/Gosora/extend" - c "github.com/Azareal/Gosora/common" + "strings" + h "github.com/Azareal/Gosora/cmd/common_hook_gen" + c "github.com/Azareal/Gosora/common" + _ "github.com/Azareal/Gosora/extend" ) // TODO: Make sure all the errors in this file propagate upwards properly @@ -22,7 +22,7 @@ func main() { debug.PrintStack() } }() - + hooks := make(map[string]int) for _, pl := range c.Plugins { if len(pl.Meta.Hooks) > 0 { @@ -45,24 +45,25 @@ func main() { } } log.Printf("hooks: %+v\n", hooks) - + imports := []string{"net/http"} - hookVars := h.HookVars{imports,nil} + hookVars := h.HookVars{imports, nil} + var params2sb strings.Builder add := func(name, params, ret, htype string, multiHook, skip bool, defaultRet, pure string) { - var params2 string first := true - for _, param := range strings.Split(params,",") { + for _, param := range strings.Split(params, ",") { if !first { - params2 += "," + params2sb.WriteRune(',') } - pspl := strings.Split(strings.ReplaceAll(strings.TrimSpace(param)," "," ")," ") - params2 += pspl[0] + pspl := strings.Split(strings.ReplaceAll(strings.TrimSpace(param), " ", " "), " ") + params2sb.WriteString(pspl[0]) first = false } - hookVars.Hooks = append(hookVars.Hooks, h.Hook{name, params, params2, ret, htype, hooks[name] > 0, multiHook, skip, defaultRet, pure}) + hookVars.Hooks = append(hookVars.Hooks, h.Hook{name, params, params2sb.String(), ret, htype, hooks[name] > 0, multiHook, skip, defaultRet, pure}) + params2sb.Reset() } - + h.AddHooks(add) h.Write(hookVars) log.Println("Successfully generated the hooks") -} \ No newline at end of file +} diff --git a/common/activity_stream.go b/common/activity_stream.go index 7fa745da..cdf9e7c0 100644 --- a/common/activity_stream.go +++ b/common/activity_stream.go @@ -25,7 +25,7 @@ type DefaultActivityStream struct { delete *sql.Stmt deleteByParams *sql.Stmt deleteByParamsExtra *sql.Stmt - aidsByParams *sql.Stmt + aidsByParams *sql.Stmt aidsByParamsExtra *sql.Stmt count *sql.Stmt } @@ -38,7 +38,7 @@ func NewDefaultActivityStream(acc *qgen.Accumulator) (*DefaultActivityStream, er delete: acc.Delete(as).Where("asid=?").Prepare(), deleteByParams: acc.Delete(as).Where("event=? AND elementID=? AND elementType=?").Prepare(), deleteByParamsExtra: acc.Delete(as).Where("event=? AND elementID=? AND elementType=? AND extra=?").Prepare(), - aidsByParams: acc.Select(as).Columns("asid").Where("event=? AND elementID=? AND elementType=?").Prepare(), + aidsByParams: acc.Select(as).Columns("asid").Where("event=? AND elementID=? AND elementType=?").Prepare(), aidsByParamsExtra: acc.Select(as).Columns("asid").Where("event=? AND elementID=? AND elementType=? AND extra=?").Prepare(), count: acc.Count(as).Prepare(), }, acc.FirstError() @@ -90,16 +90,16 @@ func (s *DefaultActivityStream) AidsByParams(event string, elementID int, elemen return aids, rows.Err() } -func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, elementType, extra string) (aids []int, err error) { - rows, err := s.aidsByParamsExtra.Query(event, elementID, elementType, extra) - if err != nil { - return nil, err +func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, elementType, extra string) (aids []int, e error) { + rows, e := s.aidsByParamsExtra.Query(event, elementID, elementType, extra) + if e != nil { + return nil, e } defer rows.Close() for rows.Next() { var aid int - if err := rows.Scan(&aid); err != nil { - return nil, err + if e := rows.Scan(&aid); e != nil { + return nil, e } aids = append(aids, aid) } @@ -109,9 +109,9 @@ func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, e // TODO: Write a test for this // Count returns the total number of activity stream items func (s *DefaultActivityStream) Count() (count int) { - err := s.count.QueryRow().Scan(&count) - if err != nil { - LogError(err) + e := s.count.QueryRow().Scan(&count) + if e != nil { + LogError(e) } return count } diff --git a/common/common.go b/common/common.go index c876675a..97d38b71 100644 --- a/common/common.go +++ b/common/common.go @@ -10,6 +10,8 @@ import ( "database/sql" "io" "log" + "net" + "net/http" "os" "strconv" "strings" @@ -291,3 +293,22 @@ func inqbuildstr(strs []string) ([]interface{}, string) { } return idList, sb.String() } + +var ConnWatch = &ConnWatcher{} + +type ConnWatcher struct { + n int64 +} + +func (cw *ConnWatcher) StateChange(conn net.Conn, state http.ConnState) { + switch state { + case http.StateNew: + atomic.AddInt64(&cw.n, 1) + case http.StateHijacked, http.StateClosed: + atomic.AddInt64(&cw.n, -1) + } +} + +func (cw *ConnWatcher) Count() int { + return int(atomic.LoadInt64(&cw.n)) +} diff --git a/common/conversations.go b/common/conversations.go index d6201695..357f2a18 100644 --- a/common/conversations.go +++ b/common/conversations.go @@ -85,25 +85,19 @@ func (co *Conversation) Posts(offset, itemsPerPage int) (posts []*ConversationPo } func (co *Conversation) PostsCount() (count int) { - err := convoStmts.countPosts.QueryRow(co.ID).Scan(&count) - if err != nil { - LogError(err) - } - return count + return Countf(convoStmts.countPosts, co.ID) } func (co *Conversation) Uids() (ids []int, err error) { - rows, err := convoStmts.getUsers.Query(co.ID) - if err != nil { - return nil, err + rows, e := convoStmts.getUsers.Query(co.ID) + if e != nil { + return nil, e } defer rows.Close() - for rows.Next() { var id int - err := rows.Scan(&id) - if err != nil { - return nil, err + if e := rows.Scan(&id); e != nil { + return nil, e } ids = append(ids, id) } @@ -111,12 +105,7 @@ func (co *Conversation) Uids() (ids []int, err error) { } func (co *Conversation) Has(uid int) (in bool) { - var count int - err := convoStmts.has.QueryRow(uid, co.ID).Scan(&count) - if err != nil { - LogError(err) - } - return count > 0 + return Countf(convoStmts.has, uid, co.ID) > 0 } func (co *Conversation) Update() error { @@ -247,7 +236,7 @@ func (s *DefaultConversationStore) GetUserExtra(uid, offset int) (cos []*Convers cmap[co.ID] = &ConversationExtra{co, nil} } - // TODO: Add a function for the q stuff + // TODO: Use inqbuild for this or a similar function var q string idList := make([]interface{}, len(raw)) for i, co := range raw { diff --git a/common/counters/performance.go b/common/counters/performance.go index 8585fc3d..2dd66e09 100644 --- a/common/counters/performance.go +++ b/common/counters/performance.go @@ -51,21 +51,22 @@ func (co *DefaultPerfCounter) Tick() error { b.Unlock() return c } + var low int64 + hTbl := c.GetHookTable() for _, b := range co.buckets { - var low int64 b.low.Lock() - low = b.low.counter - b.low.counter = math.MaxInt64 + low, b.low.counter = b.low.counter, math.MaxInt64 b.low.Unlock() if low == math.MaxInt64 { low = 0 } high := getCounter(b.high) avg := getCounter(b.avg) + c.H_counters_perf_tick_row_hook(hTbl, low, high, avg) - err := co.insertChunk(low, high, avg) // TODO: Bulk insert for speed? - if err != nil { - return errors.Wrap(errors.WithStack(err), "perf counter") + e := co.insertChunk(low, high, avg) // TODO: Bulk insert for speed? + if e != nil { + return errors.Wrap(errors.WithStack(e), "perf counter") } } return nil @@ -76,8 +77,8 @@ func (co *DefaultPerfCounter) insertChunk(low, high, avg int64) error { return nil } c.DebugLogf("Inserting a pchunk with low %d, high %d, avg %d", low, high, avg) - _, err := co.insert.Exec(low, high, avg) - return err + _, e := co.insert.Exec(low, high, avg) + return e } func (co *DefaultPerfCounter) Push(dur time.Duration /*,_ bool*/) { diff --git a/common/extend.go b/common/extend.go index 58dc1bcc..2316250c 100644 --- a/common/extend.go +++ b/common/extend.go @@ -126,6 +126,8 @@ var hookTable = &HookTable{ "tasks_tick_topic_list": nil, "tasks_tick_widget_wol": nil, + + "counters_perf_tick_row": nil, }, map[string][]func(string) string{ "preparse_preassign": nil, diff --git a/common/forum_actions.go b/common/forum_actions.go new file mode 100644 index 00000000..156abaa9 --- /dev/null +++ b/common/forum_actions.go @@ -0,0 +1,399 @@ +package common + +import ( + "database/sql" + "fmt" + "strconv" + + qgen "github.com/Azareal/Gosora/query_gen" +) + +var ForumActionStore ForumActionStoreInt + +//var ForumActionRunnableStore ForumActionRunnableStoreInt + +const ( + ForumActionDelete = iota + ForumActionLock + ForumActionUnlock + ForumActionMove +) + +func ConvStringToAct(s string) int { + switch s { + case "delete": + return ForumActionDelete + case "lock": + return ForumActionLock + case "unlock": + return ForumActionUnlock + case "move": + return ForumActionMove + } + return -1 +} +func ConvActToString(a int) string { + switch a { + case ForumActionDelete: + return "delete" + case ForumActionLock: + return "lock" + case ForumActionUnlock: + return "unlock" + case ForumActionMove: + return "move" + } + return "" +} + +var forumActionStmts ForumActionStmts + +type ForumActionStmts struct { + get1 *sql.Stmt + get2 *sql.Stmt + lock1 *sql.Stmt + lock2 *sql.Stmt + unlock1 *sql.Stmt + unlock2 *sql.Stmt +} + +type ForumAction struct { + ID int + Forum int + RunOnTopicCreation bool + RunDaysAfterTopicCreation int + RunDaysAfterTopicLastReply int + Action int + Extra string +} + +func init() { + DbInits.Add(func(acc *qgen.Accumulator) error { + t := "topics" + forumActionStmts = ForumActionStmts{ + get1: acc.Select(t).Cols("tid,createdBy,poll").Where("parentID=?").DateOlderThanQ("createdAt", "day").Stmt(), + get2: acc.Select(t).Cols("tid,createdBy,poll").Where("parentID=?").DateOlderThanQ("lastReplyAt", "day").Stmt(), + + /*lock1: acc.Update(t).Set("is_closed=1").Where("parentID=?").DateOlderThanQ("createdAt", "day").Stmt(), + lock2: acc.Update(t).Set("is_closed=1").Where("parentID=?").DateOlderThanQ("lastReplyAt", "day").Stmt(), + unlock1: acc.Update(t).Set("is_closed=0").Where("parentID=?").DateOlderThanQ("createdAt", "day").Stmt(), + unlock2: acc.Update(t).Set("is_closed=0").Where("parentID=?").DateOlderThanQ("lastReplyAt", "day").Stmt(),*/ + } + return acc.FirstError() + }) +} + +func (a *ForumAction) Run() error { + if a.RunDaysAfterTopicCreation > 0 { + if e := a.runDaysAfterTopicCreation(); e != nil { + return e + } + } + if a.RunDaysAfterTopicLastReply > 0 { + if e := a.runDaysAfterTopicLastReply(); e != nil { + return e + } + } + return nil +} + +func (a *ForumAction) runQ(stmt *sql.Stmt, days int, f func(t *Topic) error) error { + rows, e := stmt.Query(days, a.Forum) + if e != nil { + return e + } + defer rows.Close() + for rows.Next() { + // TODO: Decouple this + t := &Topic{ParentID: a.Forum} + if e := rows.Scan(&t.ID, &t.CreatedBy, &t.Poll); e != nil { + return e + } + if e = f(t); e != nil { + return e + } + } + return rows.Err() +} + +func (a *ForumAction) runDaysAfterTopicCreation() (e error) { + switch a.Action { + case ForumActionDelete: + // TODO: Bulk delete? + e = a.runQ(forumActionStmts.get1, a.RunDaysAfterTopicCreation, func(t *Topic) error { + return t.Delete() + }) + case ForumActionLock: + /*_, e := forumActionStmts.lock1.Exec(a.Forum) + if e != nil { + return e + }*/ + // TODO: Bulk lock? Lock and get resultset of changed topics somehow? + fmt.Println("ForumActionLock") + e = a.runQ(forumActionStmts.get1, a.RunDaysAfterTopicCreation, func(t *Topic) error { + fmt.Printf("t: %+v\n", t) + return t.Lock() + }) + case ForumActionUnlock: + // TODO: Bulk unlock? Unlock and get resultset of changed topics somehow? + e = a.runQ(forumActionStmts.get1, a.RunDaysAfterTopicCreation, func(t *Topic) error { + return t.Unlock() + }) + case ForumActionMove: + destForum, e := strconv.Atoi(a.Extra) + if e != nil { + return e + } + e = a.runQ(forumActionStmts.get1, a.RunDaysAfterTopicCreation, func(t *Topic) error { + return t.MoveTo(destForum) + }) + } + return e +} + +func (a *ForumAction) runDaysAfterTopicLastReply() (e error) { + switch a.Action { + case ForumActionDelete: + e = a.runQ(forumActionStmts.get2, a.RunDaysAfterTopicLastReply, func(t *Topic) error { + return t.Delete() + }) + case ForumActionLock: + // TODO: Bulk lock? Lock and get resultset of changed topics somehow? + e = a.runQ(forumActionStmts.get2, a.RunDaysAfterTopicLastReply, func(t *Topic) error { + return t.Lock() + }) + case ForumActionUnlock: + // TODO: Bulk unlock? Unlock and get resultset of changed topics somehow? + e = a.runQ(forumActionStmts.get2, a.RunDaysAfterTopicLastReply, func(t *Topic) error { + return t.Unlock() + }) + case ForumActionMove: + destForum, e := strconv.Atoi(a.Extra) + if e != nil { + return e + } + e = a.runQ(forumActionStmts.get2, a.RunDaysAfterTopicLastReply, func(t *Topic) error { + return t.MoveTo(destForum) + }) + } + return nil +} + +func (a *ForumAction) TopicCreation(tid int) error { + if !a.RunOnTopicCreation { + return nil + } + return nil +} + +type ForumActionStoreInt interface { + Get(faid int) (*ForumAction, error) + GetInForum(fid int) ([]*ForumAction, error) + GetAll() ([]*ForumAction, error) + GetNewTopicActions(fid int) ([]*ForumAction, error) + + Add(fa *ForumAction) (int, error) + Delete(faid int) error + Exists(faid int) bool + Count() int + CountInForum(fid int) int + + DailyTick() error +} + +type DefaultForumActionStore struct { + get *sql.Stmt + getInForum *sql.Stmt + getAll *sql.Stmt + getNewTopicActions *sql.Stmt + + add *sql.Stmt + delete *sql.Stmt + exists *sql.Stmt + count *sql.Stmt + countInForum *sql.Stmt +} + +func NewDefaultForumActionStore(acc *qgen.Accumulator) (*DefaultForumActionStore, error) { + fa := "forums_actions" + allCols := "faid,fid,runOnTopicCreation,runDaysAfterTopicCreation,runDaysAfterTopicLastReply,action,extra" + return &DefaultForumActionStore{ + get: acc.Select(fa).Columns("fid,runOnTopicCreation,runDaysAfterTopicCreation,runDaysAfterTopicLastReply,action,extra").Where("faid=?").Prepare(), + getInForum: acc.Select(fa).Columns("faid,runOnTopicCreation,runDaysAfterTopicCreation,runDaysAfterTopicLastReply,action,extra").Where("fid=?").Prepare(), + getAll: acc.Select(fa).Columns(allCols).Prepare(), + getNewTopicActions: acc.Select(fa).Columns(allCols).Where("fid=? AND runOnTopicCreation=1").Prepare(), + + add: acc.Insert(fa).Columns("fid,runOnTopicCreation,runDaysAfterTopicCreation,runDaysAfterTopicLastReply,action,extra").Fields("?,?,?,?,?,?").Prepare(), + delete: acc.Delete(fa).Where("faid=?").Prepare(), + exists: acc.Exists(fa, "faid").Prepare(), + count: acc.Count(fa).Prepare(), + countInForum: acc.Count(fa).Where("fid=?").Prepare(), + }, acc.FirstError() +} + +func (s *DefaultForumActionStore) DailyTick() error { + fas, e := s.GetAll() + if e != nil { + return e + } + for _, fa := range fas { + if e := fa.Run(); e != nil { + return e + } + } + return nil +} + +func (s *DefaultForumActionStore) Get(id int) (*ForumAction, error) { + fa := ForumAction{ID: id} + var str string + e := s.get.QueryRow(id).Scan(&fa.Forum, &fa.RunOnTopicCreation, &fa.RunDaysAfterTopicCreation, &fa.RunDaysAfterTopicLastReply, &str, &fa.Extra) + fa.Action = ConvStringToAct(str) + return &fa, e +} + +func (s *DefaultForumActionStore) GetInForum(fid int) (fas []*ForumAction, e error) { + rows, e := s.getInForum.Query(fid) + if e != nil { + return nil, e + } + defer rows.Close() + var str string + for rows.Next() { + fa := ForumAction{Forum: fid} + if e := rows.Scan(&fa.ID, &fa.RunOnTopicCreation, &fa.RunDaysAfterTopicCreation, &fa.RunDaysAfterTopicLastReply, &str, &fa.Extra); e != nil { + return nil, e + } + fa.Action = ConvStringToAct(str) + fas = append(fas, &fa) + } + return fas, rows.Err() +} + +func (s *DefaultForumActionStore) GetAll() (fas []*ForumAction, e error) { + rows, e := s.getAll.Query() + if e != nil { + return nil, e + } + defer rows.Close() + var str string + for rows.Next() { + fa := ForumAction{} + if e := rows.Scan(&fa.ID, &fa.Forum, &fa.RunOnTopicCreation, &fa.RunDaysAfterTopicCreation, &fa.RunDaysAfterTopicLastReply, &str, &fa.Extra); e != nil { + return nil, e + } + fa.Action = ConvStringToAct(str) + fas = append(fas, &fa) + } + return fas, rows.Err() +} + +func (s *DefaultForumActionStore) GetNewTopicActions(fid int) (fas []*ForumAction, e error) { + rows, e := s.getNewTopicActions.Query(fid) + if e != nil { + return nil, e + } + defer rows.Close() + var str string + for rows.Next() { + fa := ForumAction{RunOnTopicCreation: true} + if e := rows.Scan(&fa.ID, &fa.Forum, &fa.RunDaysAfterTopicCreation, &fa.RunDaysAfterTopicLastReply, &str, &fa.Extra); e != nil { + return nil, e + } + fa.Action = ConvStringToAct(str) + fas = append(fas, &fa) + } + return fas, rows.Err() +} + +func (s *DefaultForumActionStore) Add(fa *ForumAction) (int, error) { + res, e := s.add.Exec(fa.Forum, fa.RunOnTopicCreation, fa.RunDaysAfterTopicCreation, fa.RunDaysAfterTopicLastReply, ConvActToString(fa.Action), fa.Extra) + if e != nil { + return 0, e + } + lastID, e := res.LastInsertId() + return int(lastID), e +} + +func (s *DefaultForumActionStore) Delete(id int) error { + _, e := s.delete.Exec(id) + return e +} + +func (s *DefaultForumActionStore) Exists(id int) bool { + err := s.exists.QueryRow(id).Scan(&id) + if err != nil && err != ErrNoRows { + LogError(err) + } + return err != ErrNoRows +} + +func (s *DefaultForumActionStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) + if err != nil { + LogError(err) + } + return count +} + +func (s *DefaultForumActionStore) CountInForum(fid int) (count int) { + return Countf(s.countInForum, fid) +} + +/*type ForumActionRunnable struct { + ID int + ActionID int + TargetID int + TargetType int // 0 = topic + RunAfter int //unixtime +} + +type ForumActionRunnableStoreInt interface { + GetAfterTime(unix int) ([]*ForumActionRunnable, error) + GetInForum(fid int) ([]*ForumActionRunnable, error) + Delete(faid int) error + DeleteInForum(fid int) error + DeleteByActionID(faid int) error + Count() int + CountInForum(fid int) int +} + +type DefaultForumActionRunnableStore struct { + delete *sql.Stmt + deleteInForum *sql.Stmt + count *sql.Stmt + countInForum *sql.Stmt +} + +func NewDefaultForumActionRunnableStore(acc *qgen.Accumulator) (*DefaultForumActionRunnableStore, error) { + fa := "forums_actions" + return &DefaultForumActionRunnableStore{ + delete: acc.Delete(fa).Where("faid=?").Prepare(), + deleteInForum: acc.Delete(fa).Where("fid=?").Prepare(), + count: acc.Count(fa).Prepare(), + countInForum: acc.Count(fa).Where("faid=?").Prepare(), + }, acc.FirstError() +} + +func (s *DefaultForumActionRunnableStore) Delete(id int) error { + _, e := s.delete.Exec(id) + return e +} + +func (s *DefaultForumActionRunnableStore) DeleteInForum(fid int) error { + _, e := s.deleteInForum.Exec(id) + return e +} + +func (s *DefaultForumActionRunnableStore) Count() (count int) { + err := s.count.QueryRow().Scan(&count) + if err != nil { + LogError(err) + } + return count +} + +func (s *DefaultForumActionRunnableStore) CountInForum(fid int) (count int) { + return Countf(s.countInForum, fid) +} +*/ diff --git a/common/forum_perms_store.go b/common/forum_perms_store.go index 549cb7c2..17c0f5b9 100644 --- a/common/forum_perms_store.go +++ b/common/forum_perms_store.go @@ -52,9 +52,9 @@ func (s *MemoryForumPermsStore) Init() error { // TODO: Optimise this? func (s *MemoryForumPermsStore) ReloadAll() error { DebugLog("Reloading the forum perms") - fids, err := Forums.GetAllIDs() - if err != nil { - return err + fids, e := Forums.GetAllIDs() + if e != nil { + return e } for _, fid := range fids { if e := s.reload(fid); e != nil { @@ -68,13 +68,13 @@ func (s *MemoryForumPermsStore) ReloadAll() error { return nil } -func (s *MemoryForumPermsStore) parseForumPerm(perms []byte) (pperms *ForumPerms, err error) { +func (s *MemoryForumPermsStore) parseForumPerm(perms []byte) (pperms *ForumPerms, e error) { DebugDetail("perms: ", string(perms)) pperms = BlankForumPerms() - err = json.Unmarshal(perms, &pperms) + e = json.Unmarshal(perms, &pperms) pperms.ExtData = make(map[string]bool) pperms.Overrides = true - return pperms, err + return pperms, e } func (s *MemoryForumPermsStore) Reload(fid int) error { @@ -246,10 +246,10 @@ func (s *MemoryForumPermsStore) Get(fid, gid int) (fp *ForumPerms, err error) { // TODO: Check if the forum exists? // TODO: Fix the races -func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fp ForumPerms, err error) { - fPermsPtr, err := s.Get(fid, gid) - if err != nil { - return fp, err +func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fp ForumPerms, e error) { + fPermsPtr, e := s.Get(fid, gid) + if e != nil { + return fp, e } return *fPermsPtr, nil } diff --git a/common/forum_store.go b/common/forum_store.go index 739f08df..148ebc98 100644 --- a/common/forum_store.go +++ b/common/forum_store.go @@ -81,18 +81,21 @@ type MemoryForumStore struct { func NewMemoryForumStore() (*MemoryForumStore, error) { acc := qgen.NewAcc() f := "forums" + set := func(s string) *sql.Stmt { + return acc.Update(f).Set(s).Where("fid=?").Prepare() + } // TODO: Do a proper delete return &MemoryForumStore{ get: acc.Select(f).Columns("name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid=?").Prepare(), getAll: acc.Select(f).Columns("fid, name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Orderby("order ASC, fid ASC").Prepare(), - delete: acc.Update(f).Set("name='',active=0").Where("fid=?").Prepare(), - create: acc.Insert(f).Columns("name, desc, tmpl, active, preset").Fields("?,?,'',?,?").Prepare(), + delete: set("name='',active=0"), + create: acc.Insert(f).Columns("name,desc,tmpl,active,preset").Fields("?,?,'',?,?").Prepare(), count: acc.Count(f).Where("name != ''").Prepare(), - updateCache: acc.Update(f).Set("lastTopicID=?, lastReplyerID=?").Where("fid=?").Prepare(), - addTopics: acc.Update(f).Set("topicCount=topicCount+?").Where("fid=?").Prepare(), - removeTopics: acc.Update(f).Set("topicCount=topicCount-?").Where("fid=?").Prepare(), - lastTopic: acc.Select("topics").Columns("tid").Where("parentID=?").Orderby("lastReplyAt DESC, createdAt DESC").Limit("1").Prepare(), - updateOrder: acc.Update(f).Set("order=?").Where("fid=?").Prepare(), + updateCache: set("lastTopicID=?,lastReplyerID=?"), + addTopics: set("topicCount=topicCount+?"), + removeTopics: set("topicCount=topicCount-?"), + lastTopic: acc.Select("topics").Columns("tid").Where("parentID=?").Orderby("lastReplyAt DESC,createdAt DESC").Limit("1").Prepare(), + updateOrder: set("order=?"), }, acc.FirstError() } diff --git a/common/group.go b/common/group.go index f2a0e890..831b84d0 100644 --- a/common/group.go +++ b/common/group.go @@ -44,11 +44,13 @@ var groupStmts GroupStmts func init() { DbInits.Add(func(acc *qgen.Accumulator) error { - ug := "users_groups" + set := func(s string) *sql.Stmt { + return acc.Update("users_groups").Set(s).Where("gid=?").Prepare() + } groupStmts = GroupStmts{ - updateGroup: acc.Update(ug).Set("name=?,tag=?").Where("gid=?").Prepare(), - updateGroupRank: acc.Update(ug).Set("is_admin=?,is_mod=?,is_banned=?").Where("gid=?").Prepare(), - updateGroupPerms: acc.Update(ug).Set("permissions=?").Where("gid=?").Prepare(), + updateGroup: set("name=?,tag=?"), + updateGroupRank: set("is_admin=?,is_mod=?,is_banned=?"), + updateGroupPerms: set("permissions=?"), } return acc.FirstError() }) diff --git a/common/group_store.go b/common/group_store.go index 9a945186..8e61e7bc 100644 --- a/common/group_store.go +++ b/common/group_store.go @@ -141,22 +141,20 @@ func (s *MemoryGroupStore) GetCopy(id int) (Group, error) { func (s *MemoryGroupStore) Reload(id int) error { // TODO: Reload this data too - g, err := s.Get(id) - if err != nil { + g, e := s.Get(id) + if e != nil { LogError(errors.New("can't get cansee data for group #" + strconv.Itoa(id))) return nil } canSee := g.CanSee g = &Group{ID: id, CanSee: canSee} - err = s.get.QueryRow(id).Scan(&g.Name, &g.PermissionsText, &g.PluginPermsText, &g.IsMod, &g.IsAdmin, &g.IsBanned, &g.Tag) - if err != nil { - return err + e = s.get.QueryRow(id).Scan(&g.Name, &g.PermissionsText, &g.PluginPermsText, &g.IsMod, &g.IsAdmin, &g.IsBanned, &g.Tag) + if e != nil { + return e } - - err = s.initGroup(g) - if err != nil { - LogError(err) + if e = s.initGroup(g); e != nil { + LogError(e) return nil } @@ -166,19 +164,19 @@ func (s *MemoryGroupStore) Reload(id int) error { } func (s *MemoryGroupStore) initGroup(g *Group) error { - err := json.Unmarshal(g.PermissionsText, &g.Perms) - if err != nil { - log.Printf("group: %+v\n", g) + e := json.Unmarshal(g.PermissionsText, &g.Perms) + if e != nil { + log.Printf("g: %+v\n", g) log.Print("bad group perms: ", g.PermissionsText) - return err + return e } DebugLogf(g.Name+": %+v\n", g.Perms) - err = json.Unmarshal(g.PluginPermsText, &g.PluginPerms) - if err != nil { - log.Printf("group: %+v\n", g) + e = json.Unmarshal(g.PluginPermsText, &g.PluginPerms) + if e != nil { + log.Printf("g: %+v\n", g) log.Print("bad group plugin perms: ", g.PluginPermsText) - return err + return e } DebugLogf(g.Name+": %+v\n", g.PluginPerms) @@ -188,9 +186,9 @@ func (s *MemoryGroupStore) initGroup(g *Group) error { g.IsBanned = false } - err = s.userCount.QueryRow(g.ID).Scan(&g.UserCount) - if err != sql.ErrNoRows { - return err + e = s.userCount.QueryRow(g.ID).Scan(&g.UserCount) + if e != sql.ErrNoRows { + return e } return nil } diff --git a/common/pages.go b/common/pages.go index 26baa87a..ff2120ba 100644 --- a/common/pages.go +++ b/common/pages.go @@ -649,12 +649,18 @@ type GroupForumPermPreset struct { type PanelEditForumPage struct { *BasePanelPage - ID int - Name string - Desc string - Active bool - Preset string - Groups []GroupForumPermPreset + ID int + Name string + Desc string + Active bool + Preset string + Groups []GroupForumPermPreset + Actions []*ForumActionAction +} + +type ForumActionAction struct { + *ForumAction + ActionName string } type NameLangToggle struct { @@ -793,11 +799,12 @@ type PanelDebugPage struct { DBVersion string Uptime string - OpenConns int + DBConns int DBAdapter string Goroutines int CPUs int + HttpConns int Tasks DebugPageTasks MemStats runtime.MemStats diff --git a/common/relations.go b/common/relations.go index 90a4c99d..2d642c51 100644 --- a/common/relations.go +++ b/common/relations.go @@ -39,11 +39,11 @@ func NewDefaultBlockStore(acc *qgen.Accumulator) (*DefaultBlockStore, error) { } func (s *DefaultBlockStore) IsBlockedBy(blocker, blockee int) (bool, error) { - err := s.isBlocked.QueryRow(blocker, blockee).Scan(&blocker) - if err == ErrNoRows { + e := s.isBlocked.QueryRow(blocker, blockee).Scan(&blocker) + if e == ErrNoRows { return false, nil } - return err == nil, err + return e == nil, e } // TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? @@ -55,34 +55,33 @@ func (s *DefaultBlockStore) BulkIsBlockedBy(blockers []int, blockee int) (bool, return s.IsBlockedBy(blockers[0], blockee) } idList, q := inqbuild(blockers) - count, err := qgen.NewAcc().Count("users_blocks").Where("blocker IN(" + q + ") AND blockedUser=?").TotalP(idList...) - if err == ErrNoRows { + count, e := qgen.NewAcc().Count("users_blocks").Where("blocker IN(" + q + ") AND blockedUser=?").TotalP(idList...) + if e == ErrNoRows { return false, nil } - return count == 0, err + return count == 0, e } func (s *DefaultBlockStore) Add(blocker, blockee int) error { - _, err := s.add.Exec(blocker, blockee) - return err + _, e := s.add.Exec(blocker, blockee) + return e } func (s *DefaultBlockStore) Remove(blocker, blockee int) error { - _, err := s.remove.Exec(blocker, blockee) - return err + _, e := s.remove.Exec(blocker, blockee) + return e } func (s *DefaultBlockStore) BlockedByOffset(blocker, offset, perPage int) (uids []int, err error) { - rows, err := s.blockedBy.Query(blocker, offset, perPage) - if err != nil { - return nil, err + rows, e := s.blockedBy.Query(blocker, offset, perPage) + if e != nil { + return nil, e } defer rows.Close() for rows.Next() { var uid int - err := rows.Scan(&uid) - if err != nil { - return nil, err + if e := rows.Scan(&uid); e != nil { + return nil, e } uids = append(uids, uid) } @@ -90,9 +89,9 @@ func (s *DefaultBlockStore) BlockedByOffset(blocker, offset, perPage int) (uids } func (s *DefaultBlockStore) BlockedByCount(blocker int) (count int) { - err := s.blockedByCount.QueryRow(blocker).Scan(&count) - if err != nil { - LogError(err) + e := s.blockedByCount.QueryRow(blocker).Scan(&count) + if e != nil { + LogError(e) } return count } @@ -127,13 +126,13 @@ func NewDefaultFriendStore(acc *qgen.Accumulator) (*DefaultFriendStore, error) { } func (s *DefaultFriendStore) AddInvite(requester, target int) error { - _, err := s.addInvite.Exec(requester, target) - return err + _, e := s.addInvite.Exec(requester, target) + return e } func (s *DefaultFriendStore) Confirm(requester, target int) error { - _, err := s.confirm.Exec(requester, target) - return err + _, e := s.confirm.Exec(requester, target) + return e } func (s *DefaultFriendStore) GetOwnSentInvites(uid int) ([]FriendInvite, error) { diff --git a/common/tasks.go b/common/tasks.go index febd1e4e..6b96d100 100644 --- a/common/tasks.go +++ b/common/tasks.go @@ -11,7 +11,7 @@ import ( "log" "time" - "github.com/Azareal/Gosora/query_gen" + qgen "github.com/Azareal/Gosora/query_gen" ) type TaskStmts struct { @@ -68,18 +68,22 @@ func AddShutdownTask(task func() error) { func ScheduledHalfSecondTaskCount() int { return len(ScheduledHalfSecondTasks) } + // ScheduledSecondTaskCount is not concurrency safe func ScheduledSecondTaskCount() int { return len(ScheduledSecondTasks) } + // ScheduledFifteenMinuteTaskCount is not concurrency safe func ScheduledFifteenMinuteTaskCount() int { return len(ScheduledFifteenMinuteTasks) } + // ScheduledHourTaskCount is not concurrency safe func ScheduledHourTaskCount() int { return len(ScheduledHourTasks) } + // ShutdownTaskCount is not concurrency safe func ShutdownTaskCount() int { return len(ShutdownTasks) @@ -87,25 +91,23 @@ func ShutdownTaskCount() int { // TODO: Use AddScheduledSecondTask func HandleExpiredScheduledGroups() error { - rows, err := taskStmts.getExpiredScheduledGroups.Query() - if err != nil { - return err + rows, e := taskStmts.getExpiredScheduledGroups.Query() + if e != nil { + return e } defer rows.Close() var uid int for rows.Next() { - err := rows.Scan(&uid) - if err != nil { - return err + if e := rows.Scan(&uid); e != nil { + return e } - // Sneaky way of initialising a *User, please use the methods on the UserStore instead user := BlankUser() user.ID = uid - err = user.RevertGroupUpdate() - if err != nil { - return err + e = user.RevertGroupUpdate() + if e != nil { + return e } } return rows.Err() @@ -122,28 +124,28 @@ func HandleServerSync() error { } var lastUpdate time.Time - err := taskStmts.getSync.QueryRow().Scan(&lastUpdate) - if err != nil { - return err + e := taskStmts.getSync.QueryRow().Scan(&lastUpdate) + if e != nil { + return e } if lastUpdate.After(lastSync) { - err = Forums.LoadForums() - if err != nil { + e = Forums.LoadForums() + if e != nil { log.Print("Unable to reload the forums") - return err + return e } // TODO: Resync the groups // TODO: Resync the permissions - err = LoadSettings() - if err != nil { + e = LoadSettings() + if e != nil { log.Print("Unable to reload the settings") - return err + return e } - err = WordFilters.ReloadAll() - if err != nil { + e = WordFilters.ReloadAll() + if e != nil { log.Print("Unable to reload the word filters") - return err + return e } } return nil diff --git a/common/template_init.go b/common/template_init.go index 2485f7e0..d9100da6 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -385,7 +385,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true} debugDatabase := DebugPageDatabase{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} debugDisk := DebugPageDisk{1, 1, 1, 1, 1, 1} - dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, debugTasks, memStats, debugCache, debugDatabase, debugDisk} + dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, 1, debugTasks, memStats, debugCache, debugDatabase, debugDisk} t.AddStd("panel_debug", "c.PanelDebugPage", dpage) //t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter}) @@ -395,9 +395,9 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string if content == "" { return //log.Fatal("No content body for " + name) } - err := writeFile("./tmpl_"+name+".go", content) - if err != nil { - log.Fatal(err) + e := writeFile("./tmpl_"+name+".go", content) + if e != nil { + log.Fatal(e) } } wg.Add(1) @@ -595,9 +595,9 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri if tname != "" { tname = "_" + tname } - err := writeFile(dirPrefix+"tmpl_"+name+tname+".jgo", content) - if err != nil { - log.Fatal(err) + e := writeFile(dirPrefix+"tmpl_"+name+tname+".jgo", content) + if e != nil { + log.Fatal(e) } wg.Done() }() @@ -737,9 +737,9 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) log.Print("Writing template list") wg.Add(1) go func() { - err := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix)) - if err != nil { - log.Fatal(err) + e := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix)) + if e != nil { + log.Fatal(e) } wg.Done() }() diff --git a/common/topic.go b/common/topic.go index 2f368acc..9d1b07ab 100644 --- a/common/topic.go +++ b/common/topic.go @@ -199,6 +199,7 @@ type TopicStmts struct { deleteActivity *sql.Stmt edit *sql.Stmt setPoll *sql.Stmt + testSetCreatedAt *sql.Stmt createAction *sql.Stmt getTopicUser *sql.Stmt // TODO: Can we get rid of this? @@ -209,31 +210,35 @@ var topicStmts TopicStmts func init() { DbInits.Add(func(acc *qgen.Accumulator) error { - t := "topics" + t, w := "topics", "tid=?" + set := func(s string) *sql.Stmt { + return acc.Update(t).Set(s).Where(w).Prepare() + } topicStmts = TopicStmts{ - getRids: acc.Select("replies").Columns("rid").Where("tid=?").Orderby("rid ASC").Limit("?,?").Prepare(), + getRids: acc.Select("replies").Columns("rid").Where(w).Orderby("rid ASC").Limit("?,?").Prepare(), getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.ip, r.likeCount, r.attachCount, r.actionType", "r.createdBy=u.uid", "r.tid=?", "r.rid ASC", "?,?"), getReplies2: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.likeCount, r.attachCount, r.actionType", "r.createdBy=u.uid", "r.tid=?", "r.rid ASC", "?,?"), - getReplies3: acc.Select("replies").Columns("rid, content, createdBy, createdAt, lastEdit, lastEditBy, likeCount, attachCount, actionType").Where("tid=?").Orderby("rid ASC").Limit("?,?").Prepare(), - addReplies: acc.Update(t).Set("postCount=postCount+?, lastReplyBy=?, lastReplyAt=UTC_TIMESTAMP()").Where("tid=?").Prepare(), - updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID < ? AND tid=?").Prepare(), - lock: acc.Update(t).Set("is_closed=1").Where("tid=?").Prepare(), - unlock: acc.Update(t).Set("is_closed=0").Where("tid=?").Prepare(), - moveTo: acc.Update(t).Set("parentID=?").Where("tid=?").Prepare(), - stick: acc.Update(t).Set("sticky=1").Where("tid=?").Prepare(), - unstick: acc.Update(t).Set("sticky=0").Where("tid=?").Prepare(), + getReplies3: acc.Select("replies").Columns("rid,content,createdBy,createdAt,lastEdit,lastEditBy,likeCount,attachCount,actionType").Where(w).Orderby("rid ASC").Limit("?,?").Prepare(), + addReplies: set("postCount=postCount+?,lastReplyBy=?,lastReplyAt=UTC_TIMESTAMP()"), + updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID 1 { - err = handleLikedTopicReplies(t.ID) - if err != nil { - return err + if e = handleLikedTopicReplies(t.ID); e != nil { + return e } umap := make(map[int]struct{}) - err = handleTopicReplies(umap, t.CreatedBy, t.ID) - if err != nil { - return err + e = handleTopicReplies(umap, t.CreatedBy, t.ID) + if e != nil { + return e } - _, err = topicStmts.deleteReplies.Exec(t.ID) - if err != nil { - return err + _, e = topicStmts.deleteReplies.Exec(t.ID) + if e != nil { + return e } for uid := range umap { - err = (&User{ID: uid}).RecalcPostStats() - if err != nil { - //log.Printf("err: %+v\n", err) - return err + e = (&User{ID: uid}).RecalcPostStats() + if e != nil { + //log.Printf("e: %+v\n", e) + return e } } } - err = (&User{ID: t.CreatedBy}).RecalcPostStats() - if err != nil { - return err + e = (&User{ID: t.CreatedBy}).RecalcPostStats() + if e != nil { + return e } - err = handleTopicAttachments(t.ID) - if err != nil { - return err + e = handleTopicAttachments(t.ID) + if e != nil { + return e } - err = Subscriptions.DeleteResource(t.ID, "topic") - if err != nil { - return err + e = Subscriptions.DeleteResource(t.ID, "topic") + if e != nil { + return e } - _, err = topicStmts.deleteActivity.Exec(t.ID) - if err != nil { - return err + _, e = topicStmts.deleteActivity.Exec(t.ID) + if e != nil { + return e } if t.Poll > 0 { - err = (&Poll{ID: t.Poll}).Delete() - if err != nil { - return err + e = (&Poll{ID: t.Poll}).Delete() + if e != nil { + return e } } return nil @@ -513,9 +514,9 @@ func (t *Topic) Update(name, content string) error { } func (t *Topic) SetPoll(pollID int) error { - _, err := topicStmts.setPoll.Exec(pollID, t.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll + _, e := topicStmts.setPoll.Exec(pollID, t.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll t.cacheRemove() - return err + return e } // TODO: Have this go through the ReplyStore? @@ -543,22 +544,19 @@ func (t *Topic) CreateActionReply(action, ip string, uid int) (err error) { return err } -func GetRidsForTopic(tid, offset int) (rids []int, err error) { - rows, err := topicStmts.getRids.Query(tid, offset, Config.ItemsPerPage) - if err != nil { - return nil, err +func GetRidsForTopic(tid, offset int) (rids []int, e error) { + rows, e := topicStmts.getRids.Query(tid, offset, Config.ItemsPerPage) + if e != nil { + return nil, e } defer rows.Close() - var rid int for rows.Next() { - err := rows.Scan(&rid) - if err != nil { - return nil, err + if e := rows.Scan(&rid); e != nil { + return nil, e } rids = append(rids, rid) } - return rids, rows.Err() } diff --git a/common/weak_passwords.go b/common/weak_passwords.go index 11b3cc4d..f18af8c5 100644 --- a/common/weak_passwords.go +++ b/common/weak_passwords.go @@ -27,9 +27,9 @@ type weakpassHolder struct { func InitWeakPasswords() error { var weakpass weakpassHolder - err := unmarshalJsonFile("./config/weakpass_default.json", &weakpass) - if err != nil { - return err + e := unmarshalJsonFile("./config/weakpass_default.json", &weakpass) + if e != nil { + return e } wcon := make(map[string]struct{}) @@ -41,9 +41,9 @@ func InitWeakPasswords() error { } weakpass = weakpassHolder{} - err = unmarshalJsonFileIgnore404("./config/weakpass.json", &weakpass) - if err != nil { - return err + e = unmarshalJsonFileIgnore404("./config/weakpass.json", &weakpass) + if e != nil { + return e } for _, item := range weakpass.Contains { diff --git a/common/websockets.go b/common/websockets.go index 3df0f20b..3fb66939 100644 --- a/common/websockets.go +++ b/common/websockets.go @@ -3,7 +3,7 @@ /* * * Gosora WebSocket Subsystem -* Copyright Azareal 2017 - 2020 +* Copyright Azareal 2017 - 2021 * */ package common @@ -149,12 +149,12 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) { if wsUser.User.ID == 0 { return } - _, tid, err := ParseSEOURL(page) - if err != nil { + _, tid, e := ParseSEOURL(page) + if e != nil { return } - topic, err := Topics.Get(tid) - if err != nil { + topic, e := Topics.Get(tid) + if e != nil { return } if !Forums.Exists(topic.ParentID) { @@ -169,10 +169,10 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) { return }*/ - fperms, err := FPStore.Get(topic.ParentID, usercpy.Group) - if err == ErrNoRows { + fperms, e := FPStore.Get(topic.ParentID, usercpy.Group) + if e == ErrNoRows { fperms = BlankForumPerms() - } else if err != nil { + } else if e != nil { return } cascadeForumPerms(fperms, usercpy) @@ -202,9 +202,9 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) { default: return } - err := wsUser.SetPageForSocket(conn, page) - if err != nil { - LogError(err) + e := wsUser.SetPageForSocket(conn, page) + if e != nil { + LogError(e) } } @@ -221,7 +221,7 @@ func wsPageResume(wsUser *WSUser, conn *websocket.Conn, page string, resume int6 /*if resume >= hub.lastTick.Unix() { conn.Write([]byte("resume tooslow")) } else { - conn.Write([]byte("resume success")) + conn.Write([]byte("resume success")) }*/ default: return @@ -248,8 +248,8 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) { return } wsUser.FinalizePage(page, func() { - _, tid, err := ParseSEOURL(page) - if err != nil { + _, tid, e := ParseSEOURL(page) + if e != nil { return } topicMutex.Lock() @@ -258,8 +258,7 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) { if !ok { return } - _, ok = topic[wsUser] - if !ok { + if _, ok = topic[wsUser]; !ok { return } delete(topic, wsUser) @@ -272,9 +271,9 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) { delete(adminStatsWatchers, conn) adminStatsMutex.Unlock() } - err := wsUser.SetPageForSocket(conn, "") - if err != nil { - LogError(err) + e := wsUser.SetPageForSocket(conn, "") + if e != nil { + LogError(e) } } @@ -290,9 +289,7 @@ var adminStatsMutex sync.RWMutex func adminStatsTicker() { time.Sleep(time.Second) - lastUonline := -1 - lastGonline := -1 - lastTotonline := -1 + lastUonline, lastGonline, lastTotonline := -1, -1, -1 lastCPUPerc := -1 var lastAvailableRAM int64 = -1 var noStatUpdates, noRAMUpdates bool diff --git a/common/widget.go b/common/widget.go index fa28d0a2..bca8f291 100644 --- a/common/widget.go +++ b/common/widget.go @@ -26,11 +26,11 @@ func init() { DbInits.Add(func(acc *qgen.Accumulator) error { w := "widgets" widgetStmts = WidgetStmts{ - //getList: acc.Select(w).Columns("wid, position, side, type, active, location, data").Orderby("position ASC").Prepare(), - getDockList: acc.Select(w).Columns("wid, position, type, active, location, data").Where("side=?").Orderby("position ASC").Prepare(), + //getList: acc.Select(w).Columns("wid,position,side,type,active,location,data").Orderby("position ASC").Prepare(), + getDockList: acc.Select(w).Columns("wid,position,type,active,location,data").Where("side=?").Orderby("position ASC").Prepare(), //model: acc.SimpleModel(w,"position,type,active,location,data","wid"), delete: acc.Delete(w).Where("wid=?").Prepare(), - create: acc.Insert(w).Columns("position, side, type, active, location, data").Fields("?,?,?,?,?,?").Prepare(), + create: acc.Insert(w).Columns("position,side,type,active,location,data").Fields("?,?,?,?,?,?").Prepare(), update: acc.Update(w).Set("position=?,side=?,type=?,active=?,location=?,data=?").Where("wid=?").Prepare(), } return acc.FirstError() @@ -73,10 +73,10 @@ func (w *Widget) Delete() error { return nil } -func (w *Widget) Copy() (owidget *Widget) { - owidget = &Widget{} - *owidget = *w - return owidget +func (w *Widget) Copy() (ow *Widget) { + ow = &Widget{} + *ow = *w + return ow } // TODO: Test this diff --git a/common/ws_user.go b/common/ws_user.go index 11cc0df1..de83ed58 100644 --- a/common/ws_user.go +++ b/common/ws_user.go @@ -28,8 +28,8 @@ func (u *WSUser) Ping() error { continue } socket.conn.SetWriteDeadline(time.Now().Add(time.Minute)) - err := socket.conn.WriteMessage(websocket.PingMessage, nil) - if err != nil { + e := socket.conn.WriteMessage(websocket.PingMessage, nil) + if e != nil { socket.conn.Close() } } @@ -42,9 +42,9 @@ func (u *WSUser) WriteAll(msg string) error { if socket == nil { continue } - w, err := socket.conn.NextWriter(websocket.TextMessage) - if err != nil { - return err + w, e := socket.conn.NextWriter(websocket.TextMessage) + if e != nil { + return e } _, _ = w.Write(msgbytes) w.Close() @@ -66,8 +66,8 @@ func (u *WSUser) WriteToPageBytes(msg []byte, page string) error { if socket.Page != page { continue } - w, err := socket.conn.NextWriter(websocket.TextMessage) - if err != nil { + w, e := socket.conn.NextWriter(websocket.TextMessage) + if e != nil { continue // Skip dead sockets, a dedicated goroutine handles those } _, _ = w.Write(msg) @@ -90,8 +90,8 @@ func (u *WSUser) WriteToPageBytesMulti(msgs [][]byte, page string) error { if socket.Page != page { continue } - w, err := socket.conn.NextWriter(websocket.TextMessage) - if err != nil { + w, e := socket.conn.NextWriter(websocket.TextMessage) + if e != nil { continue // Skip dead sockets, a dedicated goroutine handles those } for _, msg := range msgs { @@ -184,7 +184,7 @@ func (u *WSUser) InPage(page string) bool { return false } -func (u *WSUser) FinalizePage(page string, handle func()) { +func (u *WSUser) FinalizePage(page string, h func()) { u.Lock() defer u.Unlock() for _, socket := range u.Sockets { @@ -195,5 +195,5 @@ func (u *WSUser) FinalizePage(page string, handle func()) { return } } - handle() + h() } diff --git a/gen_router.go b/gen_router.go index 7535459d..dd0123f4 100644 --- a/gen_router.go +++ b/gen_router.go @@ -51,6 +51,8 @@ var RouteMap = map[string]interface{}{ "panel.ForumsEditPermsSubmit": panel.ForumsEditPermsSubmit, "panel.ForumsEditPermsAdvance": panel.ForumsEditPermsAdvance, "panel.ForumsEditPermsAdvanceSubmit": panel.ForumsEditPermsAdvanceSubmit, + "panel.ForumsEditActionCreateSubmit": panel.ForumsEditActionCreateSubmit, + "panel.ForumsEditActionDeleteSubmit": panel.ForumsEditActionDeleteSubmit, "panel.Settings": panel.Settings, "panel.SettingEdit": panel.SettingEdit, "panel.SettingEditSubmit": panel.SettingEditSubmit, @@ -231,155 +233,157 @@ var routeMapEnum = map[string]int{ "panel.ForumsEditPermsSubmit": 24, "panel.ForumsEditPermsAdvance": 25, "panel.ForumsEditPermsAdvanceSubmit": 26, - "panel.Settings": 27, - "panel.SettingEdit": 28, - "panel.SettingEditSubmit": 29, - "panel.WordFilters": 30, - "panel.WordFiltersCreateSubmit": 31, - "panel.WordFiltersEdit": 32, - "panel.WordFiltersEditSubmit": 33, - "panel.WordFiltersDeleteSubmit": 34, - "panel.Pages": 35, - "panel.PagesCreateSubmit": 36, - "panel.PagesEdit": 37, - "panel.PagesEditSubmit": 38, - "panel.PagesDeleteSubmit": 39, - "panel.Themes": 40, - "panel.ThemesSetDefault": 41, - "panel.ThemesMenus": 42, - "panel.ThemesMenusEdit": 43, - "panel.ThemesMenuItemEdit": 44, - "panel.ThemesMenuItemEditSubmit": 45, - "panel.ThemesMenuItemCreateSubmit": 46, - "panel.ThemesMenuItemDeleteSubmit": 47, - "panel.ThemesMenuItemOrderSubmit": 48, - "panel.ThemesWidgets": 49, - "panel.ThemesWidgetsEditSubmit": 50, - "panel.ThemesWidgetsCreateSubmit": 51, - "panel.ThemesWidgetsDeleteSubmit": 52, - "panel.Plugins": 53, - "panel.PluginsActivate": 54, - "panel.PluginsDeactivate": 55, - "panel.PluginsInstall": 56, - "panel.Users": 57, - "panel.UsersEdit": 58, - "panel.UsersEditSubmit": 59, - "panel.UsersAvatarSubmit": 60, - "panel.UsersAvatarRemoveSubmit": 61, - "panel.AnalyticsViews": 62, - "panel.AnalyticsRoutes": 63, - "panel.AnalyticsRoutesPerf": 64, - "panel.AnalyticsAgents": 65, - "panel.AnalyticsSystems": 66, - "panel.AnalyticsLanguages": 67, - "panel.AnalyticsReferrers": 68, - "panel.AnalyticsRouteViews": 69, - "panel.AnalyticsAgentViews": 70, - "panel.AnalyticsForumViews": 71, - "panel.AnalyticsSystemViews": 72, - "panel.AnalyticsLanguageViews": 73, - "panel.AnalyticsReferrerViews": 74, - "panel.AnalyticsPosts": 75, - "panel.AnalyticsMemory": 76, - "panel.AnalyticsActiveMemory": 77, - "panel.AnalyticsTopics": 78, - "panel.AnalyticsForums": 79, - "panel.AnalyticsPerf": 80, - "panel.Groups": 81, - "panel.GroupsEdit": 82, - "panel.GroupsEditPromotions": 83, - "panel.GroupsPromotionsCreateSubmit": 84, - "panel.GroupsPromotionsDeleteSubmit": 85, - "panel.GroupsEditPerms": 86, - "panel.GroupsEditSubmit": 87, - "panel.GroupsEditPermsSubmit": 88, - "panel.GroupsCreateSubmit": 89, - "panel.Backups": 90, - "panel.LogsRegs": 91, - "panel.LogsMod": 92, - "panel.LogsAdmin": 93, - "panel.Debug": 94, - "panel.DebugTasks": 95, - "panel.Dashboard": 96, - "routes.AccountEdit": 97, - "routes.AccountEditPassword": 98, - "routes.AccountEditPasswordSubmit": 99, - "routes.AccountEditAvatarSubmit": 100, - "routes.AccountEditRevokeAvatarSubmit": 101, - "routes.AccountEditUsernameSubmit": 102, - "routes.AccountEditPrivacy": 103, - "routes.AccountEditPrivacySubmit": 104, - "routes.AccountEditMFA": 105, - "routes.AccountEditMFASetup": 106, - "routes.AccountEditMFASetupSubmit": 107, - "routes.AccountEditMFADisableSubmit": 108, - "routes.AccountEditEmail": 109, - "routes.AccountEditEmailTokenSubmit": 110, - "routes.AccountLogins": 111, - "routes.AccountBlocked": 112, - "routes.LevelList": 113, - "routes.Convos": 114, - "routes.ConvosCreate": 115, - "routes.Convo": 116, - "routes.ConvosCreateSubmit": 117, - "routes.ConvosCreateReplySubmit": 118, - "routes.ConvosDeleteReplySubmit": 119, - "routes.ConvosEditReplySubmit": 120, - "routes.RelationsBlockCreate": 121, - "routes.RelationsBlockCreateSubmit": 122, - "routes.RelationsBlockRemove": 123, - "routes.RelationsBlockRemoveSubmit": 124, - "routes.ViewProfile": 125, - "routes.BanUserSubmit": 126, - "routes.UnbanUser": 127, - "routes.ActivateUser": 128, - "routes.IPSearch": 129, - "routes.DeletePostsSubmit": 130, - "routes.CreateTopicSubmit": 131, - "routes.EditTopicSubmit": 132, - "routes.DeleteTopicSubmit": 133, - "routes.StickTopicSubmit": 134, - "routes.UnstickTopicSubmit": 135, - "routes.LockTopicSubmit": 136, - "routes.UnlockTopicSubmit": 137, - "routes.MoveTopicSubmit": 138, - "routes.LikeTopicSubmit": 139, - "routes.UnlikeTopicSubmit": 140, - "routes.AddAttachToTopicSubmit": 141, - "routes.RemoveAttachFromTopicSubmit": 142, - "routes.ViewTopic": 143, - "routes.CreateReplySubmit": 144, - "routes.ReplyEditSubmit": 145, - "routes.ReplyDeleteSubmit": 146, - "routes.ReplyLikeSubmit": 147, - "routes.ReplyUnlikeSubmit": 148, - "routes.AddAttachToReplySubmit": 149, - "routes.RemoveAttachFromReplySubmit": 150, - "routes.ProfileReplyCreateSubmit": 151, - "routes.ProfileReplyEditSubmit": 152, - "routes.ProfileReplyDeleteSubmit": 153, - "routes.PollVote": 154, - "routes.PollResults": 155, - "routes.AccountLogin": 156, - "routes.AccountRegister": 157, - "routes.AccountLogout": 158, - "routes.AccountLoginSubmit": 159, - "routes.AccountLoginMFAVerify": 160, - "routes.AccountLoginMFAVerifySubmit": 161, - "routes.AccountRegisterSubmit": 162, - "routes.AccountPasswordReset": 163, - "routes.AccountPasswordResetSubmit": 164, - "routes.AccountPasswordResetToken": 165, - "routes.AccountPasswordResetTokenSubmit": 166, - "routes.DynamicRoute": 167, - "routes.UploadedFile": 168, - "routes.StaticFile": 169, - "routes.RobotsTxt": 170, - "routes.SitemapXml": 171, - "routes.OpenSearchXml": 172, - "routes.Favicon": 173, - "routes.BadRoute": 174, - "routes.HTTPSRedirect": 175, + "panel.ForumsEditActionCreateSubmit": 27, + "panel.ForumsEditActionDeleteSubmit": 28, + "panel.Settings": 29, + "panel.SettingEdit": 30, + "panel.SettingEditSubmit": 31, + "panel.WordFilters": 32, + "panel.WordFiltersCreateSubmit": 33, + "panel.WordFiltersEdit": 34, + "panel.WordFiltersEditSubmit": 35, + "panel.WordFiltersDeleteSubmit": 36, + "panel.Pages": 37, + "panel.PagesCreateSubmit": 38, + "panel.PagesEdit": 39, + "panel.PagesEditSubmit": 40, + "panel.PagesDeleteSubmit": 41, + "panel.Themes": 42, + "panel.ThemesSetDefault": 43, + "panel.ThemesMenus": 44, + "panel.ThemesMenusEdit": 45, + "panel.ThemesMenuItemEdit": 46, + "panel.ThemesMenuItemEditSubmit": 47, + "panel.ThemesMenuItemCreateSubmit": 48, + "panel.ThemesMenuItemDeleteSubmit": 49, + "panel.ThemesMenuItemOrderSubmit": 50, + "panel.ThemesWidgets": 51, + "panel.ThemesWidgetsEditSubmit": 52, + "panel.ThemesWidgetsCreateSubmit": 53, + "panel.ThemesWidgetsDeleteSubmit": 54, + "panel.Plugins": 55, + "panel.PluginsActivate": 56, + "panel.PluginsDeactivate": 57, + "panel.PluginsInstall": 58, + "panel.Users": 59, + "panel.UsersEdit": 60, + "panel.UsersEditSubmit": 61, + "panel.UsersAvatarSubmit": 62, + "panel.UsersAvatarRemoveSubmit": 63, + "panel.AnalyticsViews": 64, + "panel.AnalyticsRoutes": 65, + "panel.AnalyticsRoutesPerf": 66, + "panel.AnalyticsAgents": 67, + "panel.AnalyticsSystems": 68, + "panel.AnalyticsLanguages": 69, + "panel.AnalyticsReferrers": 70, + "panel.AnalyticsRouteViews": 71, + "panel.AnalyticsAgentViews": 72, + "panel.AnalyticsForumViews": 73, + "panel.AnalyticsSystemViews": 74, + "panel.AnalyticsLanguageViews": 75, + "panel.AnalyticsReferrerViews": 76, + "panel.AnalyticsPosts": 77, + "panel.AnalyticsMemory": 78, + "panel.AnalyticsActiveMemory": 79, + "panel.AnalyticsTopics": 80, + "panel.AnalyticsForums": 81, + "panel.AnalyticsPerf": 82, + "panel.Groups": 83, + "panel.GroupsEdit": 84, + "panel.GroupsEditPromotions": 85, + "panel.GroupsPromotionsCreateSubmit": 86, + "panel.GroupsPromotionsDeleteSubmit": 87, + "panel.GroupsEditPerms": 88, + "panel.GroupsEditSubmit": 89, + "panel.GroupsEditPermsSubmit": 90, + "panel.GroupsCreateSubmit": 91, + "panel.Backups": 92, + "panel.LogsRegs": 93, + "panel.LogsMod": 94, + "panel.LogsAdmin": 95, + "panel.Debug": 96, + "panel.DebugTasks": 97, + "panel.Dashboard": 98, + "routes.AccountEdit": 99, + "routes.AccountEditPassword": 100, + "routes.AccountEditPasswordSubmit": 101, + "routes.AccountEditAvatarSubmit": 102, + "routes.AccountEditRevokeAvatarSubmit": 103, + "routes.AccountEditUsernameSubmit": 104, + "routes.AccountEditPrivacy": 105, + "routes.AccountEditPrivacySubmit": 106, + "routes.AccountEditMFA": 107, + "routes.AccountEditMFASetup": 108, + "routes.AccountEditMFASetupSubmit": 109, + "routes.AccountEditMFADisableSubmit": 110, + "routes.AccountEditEmail": 111, + "routes.AccountEditEmailTokenSubmit": 112, + "routes.AccountLogins": 113, + "routes.AccountBlocked": 114, + "routes.LevelList": 115, + "routes.Convos": 116, + "routes.ConvosCreate": 117, + "routes.Convo": 118, + "routes.ConvosCreateSubmit": 119, + "routes.ConvosCreateReplySubmit": 120, + "routes.ConvosDeleteReplySubmit": 121, + "routes.ConvosEditReplySubmit": 122, + "routes.RelationsBlockCreate": 123, + "routes.RelationsBlockCreateSubmit": 124, + "routes.RelationsBlockRemove": 125, + "routes.RelationsBlockRemoveSubmit": 126, + "routes.ViewProfile": 127, + "routes.BanUserSubmit": 128, + "routes.UnbanUser": 129, + "routes.ActivateUser": 130, + "routes.IPSearch": 131, + "routes.DeletePostsSubmit": 132, + "routes.CreateTopicSubmit": 133, + "routes.EditTopicSubmit": 134, + "routes.DeleteTopicSubmit": 135, + "routes.StickTopicSubmit": 136, + "routes.UnstickTopicSubmit": 137, + "routes.LockTopicSubmit": 138, + "routes.UnlockTopicSubmit": 139, + "routes.MoveTopicSubmit": 140, + "routes.LikeTopicSubmit": 141, + "routes.UnlikeTopicSubmit": 142, + "routes.AddAttachToTopicSubmit": 143, + "routes.RemoveAttachFromTopicSubmit": 144, + "routes.ViewTopic": 145, + "routes.CreateReplySubmit": 146, + "routes.ReplyEditSubmit": 147, + "routes.ReplyDeleteSubmit": 148, + "routes.ReplyLikeSubmit": 149, + "routes.ReplyUnlikeSubmit": 150, + "routes.AddAttachToReplySubmit": 151, + "routes.RemoveAttachFromReplySubmit": 152, + "routes.ProfileReplyCreateSubmit": 153, + "routes.ProfileReplyEditSubmit": 154, + "routes.ProfileReplyDeleteSubmit": 155, + "routes.PollVote": 156, + "routes.PollResults": 157, + "routes.AccountLogin": 158, + "routes.AccountRegister": 159, + "routes.AccountLogout": 160, + "routes.AccountLoginSubmit": 161, + "routes.AccountLoginMFAVerify": 162, + "routes.AccountLoginMFAVerifySubmit": 163, + "routes.AccountRegisterSubmit": 164, + "routes.AccountPasswordReset": 165, + "routes.AccountPasswordResetSubmit": 166, + "routes.AccountPasswordResetToken": 167, + "routes.AccountPasswordResetTokenSubmit": 168, + "routes.DynamicRoute": 169, + "routes.UploadedFile": 170, + "routes.StaticFile": 171, + "routes.RobotsTxt": 172, + "routes.SitemapXml": 173, + "routes.OpenSearchXml": 174, + "routes.Favicon": 175, + "routes.BadRoute": 176, + "routes.HTTPSRedirect": 177, } var reverseRouteMapEnum = map[int]string{ 0: "routes.Error", @@ -409,155 +413,157 @@ var reverseRouteMapEnum = map[int]string{ 24: "panel.ForumsEditPermsSubmit", 25: "panel.ForumsEditPermsAdvance", 26: "panel.ForumsEditPermsAdvanceSubmit", - 27: "panel.Settings", - 28: "panel.SettingEdit", - 29: "panel.SettingEditSubmit", - 30: "panel.WordFilters", - 31: "panel.WordFiltersCreateSubmit", - 32: "panel.WordFiltersEdit", - 33: "panel.WordFiltersEditSubmit", - 34: "panel.WordFiltersDeleteSubmit", - 35: "panel.Pages", - 36: "panel.PagesCreateSubmit", - 37: "panel.PagesEdit", - 38: "panel.PagesEditSubmit", - 39: "panel.PagesDeleteSubmit", - 40: "panel.Themes", - 41: "panel.ThemesSetDefault", - 42: "panel.ThemesMenus", - 43: "panel.ThemesMenusEdit", - 44: "panel.ThemesMenuItemEdit", - 45: "panel.ThemesMenuItemEditSubmit", - 46: "panel.ThemesMenuItemCreateSubmit", - 47: "panel.ThemesMenuItemDeleteSubmit", - 48: "panel.ThemesMenuItemOrderSubmit", - 49: "panel.ThemesWidgets", - 50: "panel.ThemesWidgetsEditSubmit", - 51: "panel.ThemesWidgetsCreateSubmit", - 52: "panel.ThemesWidgetsDeleteSubmit", - 53: "panel.Plugins", - 54: "panel.PluginsActivate", - 55: "panel.PluginsDeactivate", - 56: "panel.PluginsInstall", - 57: "panel.Users", - 58: "panel.UsersEdit", - 59: "panel.UsersEditSubmit", - 60: "panel.UsersAvatarSubmit", - 61: "panel.UsersAvatarRemoveSubmit", - 62: "panel.AnalyticsViews", - 63: "panel.AnalyticsRoutes", - 64: "panel.AnalyticsRoutesPerf", - 65: "panel.AnalyticsAgents", - 66: "panel.AnalyticsSystems", - 67: "panel.AnalyticsLanguages", - 68: "panel.AnalyticsReferrers", - 69: "panel.AnalyticsRouteViews", - 70: "panel.AnalyticsAgentViews", - 71: "panel.AnalyticsForumViews", - 72: "panel.AnalyticsSystemViews", - 73: "panel.AnalyticsLanguageViews", - 74: "panel.AnalyticsReferrerViews", - 75: "panel.AnalyticsPosts", - 76: "panel.AnalyticsMemory", - 77: "panel.AnalyticsActiveMemory", - 78: "panel.AnalyticsTopics", - 79: "panel.AnalyticsForums", - 80: "panel.AnalyticsPerf", - 81: "panel.Groups", - 82: "panel.GroupsEdit", - 83: "panel.GroupsEditPromotions", - 84: "panel.GroupsPromotionsCreateSubmit", - 85: "panel.GroupsPromotionsDeleteSubmit", - 86: "panel.GroupsEditPerms", - 87: "panel.GroupsEditSubmit", - 88: "panel.GroupsEditPermsSubmit", - 89: "panel.GroupsCreateSubmit", - 90: "panel.Backups", - 91: "panel.LogsRegs", - 92: "panel.LogsMod", - 93: "panel.LogsAdmin", - 94: "panel.Debug", - 95: "panel.DebugTasks", - 96: "panel.Dashboard", - 97: "routes.AccountEdit", - 98: "routes.AccountEditPassword", - 99: "routes.AccountEditPasswordSubmit", - 100: "routes.AccountEditAvatarSubmit", - 101: "routes.AccountEditRevokeAvatarSubmit", - 102: "routes.AccountEditUsernameSubmit", - 103: "routes.AccountEditPrivacy", - 104: "routes.AccountEditPrivacySubmit", - 105: "routes.AccountEditMFA", - 106: "routes.AccountEditMFASetup", - 107: "routes.AccountEditMFASetupSubmit", - 108: "routes.AccountEditMFADisableSubmit", - 109: "routes.AccountEditEmail", - 110: "routes.AccountEditEmailTokenSubmit", - 111: "routes.AccountLogins", - 112: "routes.AccountBlocked", - 113: "routes.LevelList", - 114: "routes.Convos", - 115: "routes.ConvosCreate", - 116: "routes.Convo", - 117: "routes.ConvosCreateSubmit", - 118: "routes.ConvosCreateReplySubmit", - 119: "routes.ConvosDeleteReplySubmit", - 120: "routes.ConvosEditReplySubmit", - 121: "routes.RelationsBlockCreate", - 122: "routes.RelationsBlockCreateSubmit", - 123: "routes.RelationsBlockRemove", - 124: "routes.RelationsBlockRemoveSubmit", - 125: "routes.ViewProfile", - 126: "routes.BanUserSubmit", - 127: "routes.UnbanUser", - 128: "routes.ActivateUser", - 129: "routes.IPSearch", - 130: "routes.DeletePostsSubmit", - 131: "routes.CreateTopicSubmit", - 132: "routes.EditTopicSubmit", - 133: "routes.DeleteTopicSubmit", - 134: "routes.StickTopicSubmit", - 135: "routes.UnstickTopicSubmit", - 136: "routes.LockTopicSubmit", - 137: "routes.UnlockTopicSubmit", - 138: "routes.MoveTopicSubmit", - 139: "routes.LikeTopicSubmit", - 140: "routes.UnlikeTopicSubmit", - 141: "routes.AddAttachToTopicSubmit", - 142: "routes.RemoveAttachFromTopicSubmit", - 143: "routes.ViewTopic", - 144: "routes.CreateReplySubmit", - 145: "routes.ReplyEditSubmit", - 146: "routes.ReplyDeleteSubmit", - 147: "routes.ReplyLikeSubmit", - 148: "routes.ReplyUnlikeSubmit", - 149: "routes.AddAttachToReplySubmit", - 150: "routes.RemoveAttachFromReplySubmit", - 151: "routes.ProfileReplyCreateSubmit", - 152: "routes.ProfileReplyEditSubmit", - 153: "routes.ProfileReplyDeleteSubmit", - 154: "routes.PollVote", - 155: "routes.PollResults", - 156: "routes.AccountLogin", - 157: "routes.AccountRegister", - 158: "routes.AccountLogout", - 159: "routes.AccountLoginSubmit", - 160: "routes.AccountLoginMFAVerify", - 161: "routes.AccountLoginMFAVerifySubmit", - 162: "routes.AccountRegisterSubmit", - 163: "routes.AccountPasswordReset", - 164: "routes.AccountPasswordResetSubmit", - 165: "routes.AccountPasswordResetToken", - 166: "routes.AccountPasswordResetTokenSubmit", - 167: "routes.DynamicRoute", - 168: "routes.UploadedFile", - 169: "routes.StaticFile", - 170: "routes.RobotsTxt", - 171: "routes.SitemapXml", - 172: "routes.OpenSearchXml", - 173: "routes.Favicon", - 174: "routes.BadRoute", - 175: "routes.HTTPSRedirect", + 27: "panel.ForumsEditActionCreateSubmit", + 28: "panel.ForumsEditActionDeleteSubmit", + 29: "panel.Settings", + 30: "panel.SettingEdit", + 31: "panel.SettingEditSubmit", + 32: "panel.WordFilters", + 33: "panel.WordFiltersCreateSubmit", + 34: "panel.WordFiltersEdit", + 35: "panel.WordFiltersEditSubmit", + 36: "panel.WordFiltersDeleteSubmit", + 37: "panel.Pages", + 38: "panel.PagesCreateSubmit", + 39: "panel.PagesEdit", + 40: "panel.PagesEditSubmit", + 41: "panel.PagesDeleteSubmit", + 42: "panel.Themes", + 43: "panel.ThemesSetDefault", + 44: "panel.ThemesMenus", + 45: "panel.ThemesMenusEdit", + 46: "panel.ThemesMenuItemEdit", + 47: "panel.ThemesMenuItemEditSubmit", + 48: "panel.ThemesMenuItemCreateSubmit", + 49: "panel.ThemesMenuItemDeleteSubmit", + 50: "panel.ThemesMenuItemOrderSubmit", + 51: "panel.ThemesWidgets", + 52: "panel.ThemesWidgetsEditSubmit", + 53: "panel.ThemesWidgetsCreateSubmit", + 54: "panel.ThemesWidgetsDeleteSubmit", + 55: "panel.Plugins", + 56: "panel.PluginsActivate", + 57: "panel.PluginsDeactivate", + 58: "panel.PluginsInstall", + 59: "panel.Users", + 60: "panel.UsersEdit", + 61: "panel.UsersEditSubmit", + 62: "panel.UsersAvatarSubmit", + 63: "panel.UsersAvatarRemoveSubmit", + 64: "panel.AnalyticsViews", + 65: "panel.AnalyticsRoutes", + 66: "panel.AnalyticsRoutesPerf", + 67: "panel.AnalyticsAgents", + 68: "panel.AnalyticsSystems", + 69: "panel.AnalyticsLanguages", + 70: "panel.AnalyticsReferrers", + 71: "panel.AnalyticsRouteViews", + 72: "panel.AnalyticsAgentViews", + 73: "panel.AnalyticsForumViews", + 74: "panel.AnalyticsSystemViews", + 75: "panel.AnalyticsLanguageViews", + 76: "panel.AnalyticsReferrerViews", + 77: "panel.AnalyticsPosts", + 78: "panel.AnalyticsMemory", + 79: "panel.AnalyticsActiveMemory", + 80: "panel.AnalyticsTopics", + 81: "panel.AnalyticsForums", + 82: "panel.AnalyticsPerf", + 83: "panel.Groups", + 84: "panel.GroupsEdit", + 85: "panel.GroupsEditPromotions", + 86: "panel.GroupsPromotionsCreateSubmit", + 87: "panel.GroupsPromotionsDeleteSubmit", + 88: "panel.GroupsEditPerms", + 89: "panel.GroupsEditSubmit", + 90: "panel.GroupsEditPermsSubmit", + 91: "panel.GroupsCreateSubmit", + 92: "panel.Backups", + 93: "panel.LogsRegs", + 94: "panel.LogsMod", + 95: "panel.LogsAdmin", + 96: "panel.Debug", + 97: "panel.DebugTasks", + 98: "panel.Dashboard", + 99: "routes.AccountEdit", + 100: "routes.AccountEditPassword", + 101: "routes.AccountEditPasswordSubmit", + 102: "routes.AccountEditAvatarSubmit", + 103: "routes.AccountEditRevokeAvatarSubmit", + 104: "routes.AccountEditUsernameSubmit", + 105: "routes.AccountEditPrivacy", + 106: "routes.AccountEditPrivacySubmit", + 107: "routes.AccountEditMFA", + 108: "routes.AccountEditMFASetup", + 109: "routes.AccountEditMFASetupSubmit", + 110: "routes.AccountEditMFADisableSubmit", + 111: "routes.AccountEditEmail", + 112: "routes.AccountEditEmailTokenSubmit", + 113: "routes.AccountLogins", + 114: "routes.AccountBlocked", + 115: "routes.LevelList", + 116: "routes.Convos", + 117: "routes.ConvosCreate", + 118: "routes.Convo", + 119: "routes.ConvosCreateSubmit", + 120: "routes.ConvosCreateReplySubmit", + 121: "routes.ConvosDeleteReplySubmit", + 122: "routes.ConvosEditReplySubmit", + 123: "routes.RelationsBlockCreate", + 124: "routes.RelationsBlockCreateSubmit", + 125: "routes.RelationsBlockRemove", + 126: "routes.RelationsBlockRemoveSubmit", + 127: "routes.ViewProfile", + 128: "routes.BanUserSubmit", + 129: "routes.UnbanUser", + 130: "routes.ActivateUser", + 131: "routes.IPSearch", + 132: "routes.DeletePostsSubmit", + 133: "routes.CreateTopicSubmit", + 134: "routes.EditTopicSubmit", + 135: "routes.DeleteTopicSubmit", + 136: "routes.StickTopicSubmit", + 137: "routes.UnstickTopicSubmit", + 138: "routes.LockTopicSubmit", + 139: "routes.UnlockTopicSubmit", + 140: "routes.MoveTopicSubmit", + 141: "routes.LikeTopicSubmit", + 142: "routes.UnlikeTopicSubmit", + 143: "routes.AddAttachToTopicSubmit", + 144: "routes.RemoveAttachFromTopicSubmit", + 145: "routes.ViewTopic", + 146: "routes.CreateReplySubmit", + 147: "routes.ReplyEditSubmit", + 148: "routes.ReplyDeleteSubmit", + 149: "routes.ReplyLikeSubmit", + 150: "routes.ReplyUnlikeSubmit", + 151: "routes.AddAttachToReplySubmit", + 152: "routes.RemoveAttachFromReplySubmit", + 153: "routes.ProfileReplyCreateSubmit", + 154: "routes.ProfileReplyEditSubmit", + 155: "routes.ProfileReplyDeleteSubmit", + 156: "routes.PollVote", + 157: "routes.PollResults", + 158: "routes.AccountLogin", + 159: "routes.AccountRegister", + 160: "routes.AccountLogout", + 161: "routes.AccountLoginSubmit", + 162: "routes.AccountLoginMFAVerify", + 163: "routes.AccountLoginMFAVerifySubmit", + 164: "routes.AccountRegisterSubmit", + 165: "routes.AccountPasswordReset", + 166: "routes.AccountPasswordResetSubmit", + 167: "routes.AccountPasswordResetToken", + 168: "routes.AccountPasswordResetTokenSubmit", + 169: "routes.DynamicRoute", + 170: "routes.UploadedFile", + 171: "routes.StaticFile", + 172: "routes.RobotsTxt", + 173: "routes.SitemapXml", + 174: "routes.OpenSearchXml", + 175: "routes.Favicon", + 176: "routes.BadRoute", + 177: "routes.HTTPSRedirect", } var osMapEnum = map[string]int{ "unknown": 0, @@ -924,7 +930,7 @@ type HTTPSRedirect struct {} func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.Header().Set("Connection", "close") - co.RouteViewCounter.Bump(175) + co.RouteViewCounter.Bump(177) dest := "https://" + req.Host + req.URL.String() http.Redirect(w, req, dest, http.StatusTemporaryRedirect) } @@ -1047,7 +1053,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if prefix == "/s" { //old prefix: /static if !c.Config.DisableAnalytics { - co.RouteViewCounter.Bump(169) + co.RouteViewCounter.Bump(171) } routes.StaticFile(w, req) return @@ -1512,12 +1518,28 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = panel.ForumsEditPermsAdvanceSubmit(w,req,user,extraData) co.RouteViewCounter.Bump3(26, cn) + case "/panel/forums/action/create/submit/": + err = c.NoSessionMismatch(w,req,user) + if err != nil { + return err + } + + err = panel.ForumsEditActionCreateSubmit(w,req,user,extraData) + co.RouteViewCounter.Bump3(27, cn) + case "/panel/forums/action/delete/submit/": + err = c.NoSessionMismatch(w,req,user) + if err != nil { + return err + } + + err = panel.ForumsEditActionDeleteSubmit(w,req,user,extraData) + co.RouteViewCounter.Bump3(28, cn) case "/panel/settings/": err = panel.Settings(w,req,user) - co.RouteViewCounter.Bump3(27, cn) + co.RouteViewCounter.Bump3(29, cn) case "/panel/settings/edit/": err = panel.SettingEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(28, cn) + co.RouteViewCounter.Bump3(30, cn) case "/panel/settings/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1525,10 +1547,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.SettingEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(29, cn) + co.RouteViewCounter.Bump3(31, cn) case "/panel/settings/word-filters/": err = panel.WordFilters(w,req,user) - co.RouteViewCounter.Bump3(30, cn) + co.RouteViewCounter.Bump3(32, cn) case "/panel/settings/word-filters/create/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1536,10 +1558,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.WordFiltersCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(31, cn) + co.RouteViewCounter.Bump3(33, cn) case "/panel/settings/word-filters/edit/": err = panel.WordFiltersEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(32, cn) + co.RouteViewCounter.Bump3(34, cn) case "/panel/settings/word-filters/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1547,7 +1569,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.WordFiltersEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(33, cn) + co.RouteViewCounter.Bump3(35, cn) case "/panel/settings/word-filters/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1555,7 +1577,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.WordFiltersDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(34, cn) + co.RouteViewCounter.Bump3(36, cn) case "/panel/pages/": err = c.AdminOnly(w,req,user) if err != nil { @@ -1563,7 +1585,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.Pages(w,req,user) - co.RouteViewCounter.Bump3(35, cn) + co.RouteViewCounter.Bump3(37, cn) case "/panel/pages/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1576,7 +1598,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PagesCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(36, cn) + co.RouteViewCounter.Bump3(38, cn) case "/panel/pages/edit/": err = c.AdminOnly(w,req,user) if err != nil { @@ -1584,7 +1606,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PagesEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(37, cn) + co.RouteViewCounter.Bump3(39, cn) case "/panel/pages/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1597,7 +1619,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PagesEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(38, cn) + co.RouteViewCounter.Bump3(40, cn) case "/panel/pages/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1610,10 +1632,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PagesDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(39, cn) + co.RouteViewCounter.Bump3(41, cn) case "/panel/themes/": err = panel.Themes(w,req,user) - co.RouteViewCounter.Bump3(40, cn) + co.RouteViewCounter.Bump3(42, cn) case "/panel/themes/default/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1621,16 +1643,16 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesSetDefault(w,req,user,extraData) - co.RouteViewCounter.Bump3(41, cn) + co.RouteViewCounter.Bump3(43, cn) case "/panel/themes/menus/": err = panel.ThemesMenus(w,req,user) - co.RouteViewCounter.Bump3(42, cn) + co.RouteViewCounter.Bump3(44, cn) case "/panel/themes/menus/edit/": err = panel.ThemesMenusEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(43, cn) + co.RouteViewCounter.Bump3(45, cn) case "/panel/themes/menus/item/edit/": err = panel.ThemesMenuItemEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(44, cn) + co.RouteViewCounter.Bump3(46, cn) case "/panel/themes/menus/item/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1638,7 +1660,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesMenuItemEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(45, cn) + co.RouteViewCounter.Bump3(47, cn) case "/panel/themes/menus/item/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1646,7 +1668,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesMenuItemCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(46, cn) + co.RouteViewCounter.Bump3(48, cn) case "/panel/themes/menus/item/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1654,7 +1676,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesMenuItemDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(47, cn) + co.RouteViewCounter.Bump3(49, cn) case "/panel/themes/menus/item/order/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1662,10 +1684,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesMenuItemOrderSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(48, cn) + co.RouteViewCounter.Bump3(50, cn) case "/panel/themes/widgets/": err = panel.ThemesWidgets(w,req,user) - co.RouteViewCounter.Bump3(49, cn) + co.RouteViewCounter.Bump3(51, cn) case "/panel/themes/widgets/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1673,7 +1695,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesWidgetsEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(50, cn) + co.RouteViewCounter.Bump3(52, cn) case "/panel/themes/widgets/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1681,7 +1703,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesWidgetsCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(51, cn) + co.RouteViewCounter.Bump3(53, cn) case "/panel/themes/widgets/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1689,10 +1711,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.ThemesWidgetsDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(52, cn) + co.RouteViewCounter.Bump3(54, cn) case "/panel/plugins/": err = panel.Plugins(w,req,user) - co.RouteViewCounter.Bump3(53, cn) + co.RouteViewCounter.Bump3(55, cn) case "/panel/plugins/activate/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1700,7 +1722,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PluginsActivate(w,req,user,extraData) - co.RouteViewCounter.Bump3(54, cn) + co.RouteViewCounter.Bump3(56, cn) case "/panel/plugins/deactivate/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1708,7 +1730,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PluginsDeactivate(w,req,user,extraData) - co.RouteViewCounter.Bump3(55, cn) + co.RouteViewCounter.Bump3(57, cn) case "/panel/plugins/install/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1716,13 +1738,13 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.PluginsInstall(w,req,user,extraData) - co.RouteViewCounter.Bump3(56, cn) + co.RouteViewCounter.Bump3(58, cn) case "/panel/users/": err = panel.Users(w,req,user) - co.RouteViewCounter.Bump3(57, cn) + co.RouteViewCounter.Bump3(59, cn) case "/panel/users/edit/": err = panel.UsersEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(58, cn) + co.RouteViewCounter.Bump3(60, cn) case "/panel/users/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1730,7 +1752,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.UsersEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(59, cn) + co.RouteViewCounter.Bump3(61, cn) case "/panel/users/avatar/submit/": err = c.HandleUploadRoute(w,req,user,int(c.Config.MaxRequestSize)) if err != nil { @@ -1742,7 +1764,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.UsersAvatarSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(60, cn) + co.RouteViewCounter.Bump3(62, cn) case "/panel/users/avatar/remove/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1750,7 +1772,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.UsersAvatarRemoveSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(61, cn) + co.RouteViewCounter.Bump3(63, cn) case "/panel/analytics/views/": err = c.ParseForm(w,req,user) if err != nil { @@ -1758,7 +1780,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsViews(w,req,user) - co.RouteViewCounter.Bump3(62, cn) + co.RouteViewCounter.Bump3(64, cn) case "/panel/analytics/routes/": err = c.ParseForm(w,req,user) if err != nil { @@ -1766,7 +1788,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsRoutes(w,req,user) - co.RouteViewCounter.Bump3(63, cn) + co.RouteViewCounter.Bump3(65, cn) case "/panel/analytics/routes-perf/": err = c.ParseForm(w,req,user) if err != nil { @@ -1774,7 +1796,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsRoutesPerf(w,req,user) - co.RouteViewCounter.Bump3(64, cn) + co.RouteViewCounter.Bump3(66, cn) case "/panel/analytics/agents/": err = c.ParseForm(w,req,user) if err != nil { @@ -1782,7 +1804,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsAgents(w,req,user) - co.RouteViewCounter.Bump3(65, cn) + co.RouteViewCounter.Bump3(67, cn) case "/panel/analytics/systems/": err = c.ParseForm(w,req,user) if err != nil { @@ -1790,7 +1812,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsSystems(w,req,user) - co.RouteViewCounter.Bump3(66, cn) + co.RouteViewCounter.Bump3(68, cn) case "/panel/analytics/langs/": err = c.ParseForm(w,req,user) if err != nil { @@ -1798,7 +1820,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsLanguages(w,req,user) - co.RouteViewCounter.Bump3(67, cn) + co.RouteViewCounter.Bump3(69, cn) case "/panel/analytics/referrers/": err = c.ParseForm(w,req,user) if err != nil { @@ -1806,25 +1828,25 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsReferrers(w,req,user) - co.RouteViewCounter.Bump3(68, cn) + co.RouteViewCounter.Bump3(70, cn) case "/panel/analytics/route/": err = panel.AnalyticsRouteViews(w,req,user,extraData) - co.RouteViewCounter.Bump3(69, cn) + co.RouteViewCounter.Bump3(71, cn) case "/panel/analytics/agent/": err = panel.AnalyticsAgentViews(w,req,user,extraData) - co.RouteViewCounter.Bump3(70, cn) + co.RouteViewCounter.Bump3(72, cn) case "/panel/analytics/forum/": err = panel.AnalyticsForumViews(w,req,user,extraData) - co.RouteViewCounter.Bump3(71, cn) + co.RouteViewCounter.Bump3(73, cn) case "/panel/analytics/system/": err = panel.AnalyticsSystemViews(w,req,user,extraData) - co.RouteViewCounter.Bump3(72, cn) + co.RouteViewCounter.Bump3(74, cn) case "/panel/analytics/lang/": err = panel.AnalyticsLanguageViews(w,req,user,extraData) - co.RouteViewCounter.Bump3(73, cn) + co.RouteViewCounter.Bump3(75, cn) case "/panel/analytics/referrer/": err = panel.AnalyticsReferrerViews(w,req,user,extraData) - co.RouteViewCounter.Bump3(74, cn) + co.RouteViewCounter.Bump3(76, cn) case "/panel/analytics/posts/": err = c.ParseForm(w,req,user) if err != nil { @@ -1832,7 +1854,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsPosts(w,req,user) - co.RouteViewCounter.Bump3(75, cn) + co.RouteViewCounter.Bump3(77, cn) case "/panel/analytics/memory/": err = c.ParseForm(w,req,user) if err != nil { @@ -1840,7 +1862,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsMemory(w,req,user) - co.RouteViewCounter.Bump3(76, cn) + co.RouteViewCounter.Bump3(78, cn) case "/panel/analytics/active-memory/": err = c.ParseForm(w,req,user) if err != nil { @@ -1848,7 +1870,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsActiveMemory(w,req,user) - co.RouteViewCounter.Bump3(77, cn) + co.RouteViewCounter.Bump3(79, cn) case "/panel/analytics/topics/": err = c.ParseForm(w,req,user) if err != nil { @@ -1856,7 +1878,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsTopics(w,req,user) - co.RouteViewCounter.Bump3(78, cn) + co.RouteViewCounter.Bump3(80, cn) case "/panel/analytics/forums/": err = c.ParseForm(w,req,user) if err != nil { @@ -1864,7 +1886,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsForums(w,req,user) - co.RouteViewCounter.Bump3(79, cn) + co.RouteViewCounter.Bump3(81, cn) case "/panel/analytics/perf/": err = c.ParseForm(w,req,user) if err != nil { @@ -1872,16 +1894,16 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.AnalyticsPerf(w,req,user) - co.RouteViewCounter.Bump3(80, cn) + co.RouteViewCounter.Bump3(82, cn) case "/panel/groups/": err = panel.Groups(w,req,user) - co.RouteViewCounter.Bump3(81, cn) + co.RouteViewCounter.Bump3(83, cn) case "/panel/groups/edit/": err = panel.GroupsEdit(w,req,user,extraData) - co.RouteViewCounter.Bump3(82, cn) + co.RouteViewCounter.Bump3(84, cn) case "/panel/groups/edit/promotions/": err = panel.GroupsEditPromotions(w,req,user,extraData) - co.RouteViewCounter.Bump3(83, cn) + co.RouteViewCounter.Bump3(85, cn) case "/panel/groups/promotions/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1889,7 +1911,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.GroupsPromotionsCreateSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(84, cn) + co.RouteViewCounter.Bump3(86, cn) case "/panel/groups/promotions/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1897,10 +1919,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.GroupsPromotionsDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(85, cn) + co.RouteViewCounter.Bump3(87, cn) case "/panel/groups/edit/perms/": err = panel.GroupsEditPerms(w,req,user,extraData) - co.RouteViewCounter.Bump3(86, cn) + co.RouteViewCounter.Bump3(88, cn) case "/panel/groups/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1908,7 +1930,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.GroupsEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(87, cn) + co.RouteViewCounter.Bump3(89, cn) case "/panel/groups/edit/perms/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1916,7 +1938,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.GroupsEditPermsSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(88, cn) + co.RouteViewCounter.Bump3(90, cn) case "/panel/groups/create/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -1924,7 +1946,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.GroupsCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(89, cn) + co.RouteViewCounter.Bump3(91, cn) case "/panel/backups/": err = c.SuperAdminOnly(w,req,user) if err != nil { @@ -1933,16 +1955,16 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * w = r.responseWriter(w) err = panel.Backups(w,req,user,extraData) - co.RouteViewCounter.Bump3(90, cn) + co.RouteViewCounter.Bump3(92, cn) case "/panel/logs/regs/": err = panel.LogsRegs(w,req,user) - co.RouteViewCounter.Bump3(91, cn) + co.RouteViewCounter.Bump3(93, cn) case "/panel/logs/mod/": err = panel.LogsMod(w,req,user) - co.RouteViewCounter.Bump3(92, cn) + co.RouteViewCounter.Bump3(94, cn) case "/panel/logs/admin/": err = panel.LogsAdmin(w,req,user) - co.RouteViewCounter.Bump3(93, cn) + co.RouteViewCounter.Bump3(95, cn) case "/panel/debug/": err = c.AdminOnly(w,req,user) if err != nil { @@ -1950,7 +1972,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.Debug(w,req,user) - co.RouteViewCounter.Bump3(94, cn) + co.RouteViewCounter.Bump3(96, cn) case "/panel/debug/tasks/": err = c.AdminOnly(w,req,user) if err != nil { @@ -1958,10 +1980,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = panel.DebugTasks(w,req,user) - co.RouteViewCounter.Bump3(95, cn) + co.RouteViewCounter.Bump3(97, cn) default: err = panel.Dashboard(w,req,user) - co.RouteViewCounter.Bump3(96, cn) + co.RouteViewCounter.Bump3(98, cn) } case "/user": switch(req.URL.Path) { @@ -1976,7 +1998,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountEdit(w,req,user,h) - co.RouteViewCounter.Bump3(97, cn) + co.RouteViewCounter.Bump3(99, cn) case "/user/edit/password/": err = c.MemberOnly(w,req,user) if err != nil { @@ -1988,7 +2010,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountEditPassword(w,req,user,h) - co.RouteViewCounter.Bump3(98, cn) + co.RouteViewCounter.Bump3(100, cn) case "/user/edit/password/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2001,7 +2023,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditPasswordSubmit(w,req,user) - co.RouteViewCounter.Bump3(99, cn) + co.RouteViewCounter.Bump3(101, cn) case "/user/edit/avatar/submit/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2018,7 +2040,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditAvatarSubmit(w,req,user) - co.RouteViewCounter.Bump3(100, cn) + co.RouteViewCounter.Bump3(102, cn) case "/user/edit/avatar/revoke/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2031,7 +2053,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditRevokeAvatarSubmit(w,req,user) - co.RouteViewCounter.Bump3(101, cn) + co.RouteViewCounter.Bump3(103, cn) case "/user/edit/username/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2044,7 +2066,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditUsernameSubmit(w,req,user) - co.RouteViewCounter.Bump3(102, cn) + co.RouteViewCounter.Bump3(104, cn) case "/user/edit/privacy/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2056,7 +2078,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountEditPrivacy(w,req,user,h) - co.RouteViewCounter.Bump3(103, cn) + co.RouteViewCounter.Bump3(105, cn) case "/user/edit/privacy/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2069,7 +2091,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditPrivacySubmit(w,req,user) - co.RouteViewCounter.Bump3(104, cn) + co.RouteViewCounter.Bump3(106, cn) case "/user/edit/mfa/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2081,7 +2103,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountEditMFA(w,req,user,h) - co.RouteViewCounter.Bump3(105, cn) + co.RouteViewCounter.Bump3(107, cn) case "/user/edit/mfa/setup/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2093,7 +2115,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountEditMFASetup(w,req,user,h) - co.RouteViewCounter.Bump3(106, cn) + co.RouteViewCounter.Bump3(108, cn) case "/user/edit/mfa/setup/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2106,7 +2128,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditMFASetupSubmit(w,req,user) - co.RouteViewCounter.Bump3(107, cn) + co.RouteViewCounter.Bump3(109, cn) case "/user/edit/mfa/disable/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2119,7 +2141,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountEditMFADisableSubmit(w,req,user) - co.RouteViewCounter.Bump3(108, cn) + co.RouteViewCounter.Bump3(110, cn) case "/user/edit/email/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2131,10 +2153,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountEditEmail(w,req,user,h) - co.RouteViewCounter.Bump3(109, cn) + co.RouteViewCounter.Bump3(111, cn) case "/user/edit/token/": err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(110, cn) + co.RouteViewCounter.Bump3(112, cn) case "/user/edit/logins/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2146,7 +2168,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountLogins(w,req,user,h) - co.RouteViewCounter.Bump3(111, cn) + co.RouteViewCounter.Bump3(113, cn) case "/user/edit/blocked/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2158,7 +2180,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountBlocked(w,req,user,h) - co.RouteViewCounter.Bump3(112, cn) + co.RouteViewCounter.Bump3(114, cn) case "/user/levels/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2170,7 +2192,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.LevelList(w,req,user,h) - co.RouteViewCounter.Bump3(113, cn) + co.RouteViewCounter.Bump3(115, cn) case "/user/convos/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2182,7 +2204,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.Convos(w,req,user,h) - co.RouteViewCounter.Bump3(114, cn) + co.RouteViewCounter.Bump3(116, cn) case "/user/convos/create/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2194,7 +2216,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.ConvosCreate(w,req,user,h) - co.RouteViewCounter.Bump3(115, cn) + co.RouteViewCounter.Bump3(117, cn) case "/user/convo/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2206,7 +2228,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.Convo(w,req,user,h,extraData) - co.RouteViewCounter.Bump3(116, cn) + co.RouteViewCounter.Bump3(118, cn) case "/user/convos/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2219,7 +2241,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ConvosCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(117, cn) + co.RouteViewCounter.Bump3(119, cn) case "/user/convo/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2232,7 +2254,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ConvosCreateReplySubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(118, cn) + co.RouteViewCounter.Bump3(120, cn) case "/user/convo/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2245,7 +2267,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ConvosDeleteReplySubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(119, cn) + co.RouteViewCounter.Bump3(121, cn) case "/user/convo/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2258,7 +2280,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ConvosEditReplySubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(120, cn) + co.RouteViewCounter.Bump3(122, cn) case "/user/block/create/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2270,7 +2292,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.RelationsBlockCreate(w,req,user,h,extraData) - co.RouteViewCounter.Bump3(121, cn) + co.RouteViewCounter.Bump3(123, cn) case "/user/block/create/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2283,7 +2305,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.RelationsBlockCreateSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(122, cn) + co.RouteViewCounter.Bump3(124, cn) case "/user/block/remove/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2295,7 +2317,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.RelationsBlockRemove(w,req,user,h,extraData) - co.RouteViewCounter.Bump3(123, cn) + co.RouteViewCounter.Bump3(125, cn) case "/user/block/remove/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2308,7 +2330,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.RelationsBlockRemoveSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(124, cn) + co.RouteViewCounter.Bump3(126, cn) default: req.URL.Path += extraData h, err := c.UserCheckNano(w,req,user,cn) @@ -2316,7 +2338,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.ViewProfile(w,req,user,h) - co.RouteViewCounter.Bump3(125, cn) + co.RouteViewCounter.Bump3(127, cn) } case "/users": err = c.MemberOnly(w,req,user) @@ -2332,7 +2354,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.BanUserSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(126, cn) + co.RouteViewCounter.Bump3(128, cn) case "/users/unban/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2340,7 +2362,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.UnbanUser(w,req,user,extraData) - co.RouteViewCounter.Bump3(127, cn) + co.RouteViewCounter.Bump3(129, cn) case "/users/activate/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2348,14 +2370,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ActivateUser(w,req,user,extraData) - co.RouteViewCounter.Bump3(128, cn) + co.RouteViewCounter.Bump3(130, cn) case "/users/ips/": h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err } err = routes.IPSearch(w,req,user,h) - co.RouteViewCounter.Bump3(129, cn) + co.RouteViewCounter.Bump3(131, cn) case "/users/delete-posts/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2363,7 +2385,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.DeletePostsSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(130, cn) + co.RouteViewCounter.Bump3(132, cn) } case "/topic": switch(req.URL.Path) { @@ -2383,7 +2405,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.CreateTopicSubmit(w,req,user) - co.RouteViewCounter.Bump3(131, cn) + co.RouteViewCounter.Bump3(133, cn) case "/topic/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2396,7 +2418,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.EditTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(132, cn) + co.RouteViewCounter.Bump3(134, cn) case "/topic/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2410,7 +2432,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * req.URL.Path += extraData err = routes.DeleteTopicSubmit(w,req,user) - co.RouteViewCounter.Bump3(133, cn) + co.RouteViewCounter.Bump3(135, cn) case "/topic/stick/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2423,7 +2445,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.StickTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(134, cn) + co.RouteViewCounter.Bump3(136, cn) case "/topic/unstick/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2436,7 +2458,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.UnstickTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(135, cn) + co.RouteViewCounter.Bump3(137, cn) case "/topic/lock/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2450,7 +2472,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * req.URL.Path += extraData err = routes.LockTopicSubmit(w,req,user) - co.RouteViewCounter.Bump3(136, cn) + co.RouteViewCounter.Bump3(138, cn) case "/topic/unlock/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2463,7 +2485,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.UnlockTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(137, cn) + co.RouteViewCounter.Bump3(139, cn) case "/topic/move/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2476,7 +2498,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.MoveTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(138, cn) + co.RouteViewCounter.Bump3(140, cn) case "/topic/like/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2489,7 +2511,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.LikeTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(139, cn) + co.RouteViewCounter.Bump3(141, cn) case "/topic/unlike/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2502,7 +2524,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.UnlikeTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(140, cn) + co.RouteViewCounter.Bump3(142, cn) case "/topic/attach/add/submit/": err = c.MemberOnly(w,req,user) if err != nil { @@ -2519,7 +2541,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AddAttachToTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(141, cn) + co.RouteViewCounter.Bump3(143, cn) case "/topic/attach/remove/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2532,14 +2554,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(142, cn) + co.RouteViewCounter.Bump3(144, cn) default: h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err } err = routes.ViewTopic(w,req,user,h,extraData) - co.RouteViewCounter.Bump3(143, cn) + co.RouteViewCounter.Bump3(145, cn) } case "/reply": err = c.MemberOnly(w,req,user) @@ -2559,7 +2581,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.CreateReplySubmit(w,req,user) - co.RouteViewCounter.Bump3(144, cn) + co.RouteViewCounter.Bump3(146, cn) case "/reply/edit/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2567,7 +2589,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ReplyEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(145, cn) + co.RouteViewCounter.Bump3(147, cn) case "/reply/delete/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2575,7 +2597,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ReplyDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(146, cn) + co.RouteViewCounter.Bump3(148, cn) case "/reply/like/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2583,7 +2605,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ReplyLikeSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(147, cn) + co.RouteViewCounter.Bump3(149, cn) case "/reply/unlike/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2591,7 +2613,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.ReplyUnlikeSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(148, cn) + co.RouteViewCounter.Bump3(150, cn) case "/reply/attach/add/submit/": err = c.HandleUploadRoute(w,req,user,int(c.Config.MaxRequestSize)) if err != nil { @@ -2603,7 +2625,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AddAttachToReplySubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(149, cn) + co.RouteViewCounter.Bump3(151, cn) case "/reply/attach/remove/submit/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2611,15 +2633,15 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(150, cn) + co.RouteViewCounter.Bump3(152, cn) } case "/profile": - err = c.MemberOnly(w,req,user) + err = c.NoSessionMismatch(w,req,user) if err != nil { return err } - err = c.NoSessionMismatch(w,req,user) + err = c.MemberOnly(w,req,user) if err != nil { return err } @@ -2627,13 +2649,13 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * switch(req.URL.Path) { case "/profile/reply/create/": err = routes.ProfileReplyCreateSubmit(w,req,user) - co.RouteViewCounter.Bump3(151, cn) + co.RouteViewCounter.Bump3(153, cn) case "/profile/reply/edit/submit/": err = routes.ProfileReplyEditSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(152, cn) + co.RouteViewCounter.Bump3(154, cn) case "/profile/reply/delete/submit/": err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) - co.RouteViewCounter.Bump3(153, cn) + co.RouteViewCounter.Bump3(155, cn) } case "/poll": switch(req.URL.Path) { @@ -2649,10 +2671,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.PollVote(w,req,user,extraData) - co.RouteViewCounter.Bump3(154, cn) + co.RouteViewCounter.Bump3(156, cn) case "/poll/results/": err = routes.PollResults(w,req,user,extraData) - co.RouteViewCounter.Bump3(155, cn) + co.RouteViewCounter.Bump3(157, cn) } case "/accounts": switch(req.URL.Path) { @@ -2662,14 +2684,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * return err } err = routes.AccountLogin(w,req,user,h) - co.RouteViewCounter.Bump3(156, cn) + co.RouteViewCounter.Bump3(158, cn) case "/accounts/create/": h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err } err = routes.AccountRegister(w,req,user,h) - co.RouteViewCounter.Bump3(157, cn) + co.RouteViewCounter.Bump3(159, cn) case "/accounts/logout/": err = c.NoSessionMismatch(w,req,user) if err != nil { @@ -2682,7 +2704,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountLogout(w,req,user) - co.RouteViewCounter.Bump3(158, cn) + co.RouteViewCounter.Bump3(160, cn) case "/accounts/login/submit/": err = c.ParseForm(w,req,user) if err != nil { @@ -2690,14 +2712,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountLoginSubmit(w,req,user) - co.RouteViewCounter.Bump3(159, cn) + co.RouteViewCounter.Bump3(161, cn) case "/accounts/mfa_verify/": h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err } err = routes.AccountLoginMFAVerify(w,req,user,h) - co.RouteViewCounter.Bump3(160, cn) + co.RouteViewCounter.Bump3(162, cn) case "/accounts/mfa_verify/submit/": err = c.ParseForm(w,req,user) if err != nil { @@ -2705,7 +2727,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountLoginMFAVerifySubmit(w,req,user) - co.RouteViewCounter.Bump3(161, cn) + co.RouteViewCounter.Bump3(163, cn) case "/accounts/create/submit/": err = c.ParseForm(w,req,user) if err != nil { @@ -2713,14 +2735,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountRegisterSubmit(w,req,user) - co.RouteViewCounter.Bump3(162, cn) + co.RouteViewCounter.Bump3(164, cn) case "/accounts/password-reset/": h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err } err = routes.AccountPasswordReset(w,req,user,h) - co.RouteViewCounter.Bump3(163, cn) + co.RouteViewCounter.Bump3(165, cn) case "/accounts/password-reset/submit/": err = c.ParseForm(w,req,user) if err != nil { @@ -2728,14 +2750,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountPasswordResetSubmit(w,req,user) - co.RouteViewCounter.Bump3(164, cn) + co.RouteViewCounter.Bump3(166, cn) case "/accounts/password-reset/token/": h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err } err = routes.AccountPasswordResetToken(w,req,user,h) - co.RouteViewCounter.Bump3(165, cn) + co.RouteViewCounter.Bump3(167, cn) case "/accounts/password-reset/token/submit/": err = c.ParseForm(w,req,user) if err != nil { @@ -2743,7 +2765,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * } err = routes.AccountPasswordResetTokenSubmit(w,req,user) - co.RouteViewCounter.Bump3(166, cn) + co.RouteViewCounter.Bump3(168, cn) } /*case "/sitemaps": // TODO: Count these views req.URL.Path += extraData @@ -2754,33 +2776,33 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * http.Redirect(w, req, "/s/"+extraData, http.StatusTemporaryRedirect) case "/uploads": if extraData == "" { - co.RouteViewCounter.Bump3(168, cn) + co.RouteViewCounter.Bump3(170, cn) return c.NotFound(w,req,nil) } w = r.responseWriter(w) req.URL.Path += extraData // TODO: Find a way to propagate errors up from this? r.UploadHandler(w,req) // TODO: Count these views - co.RouteViewCounter.Bump3(168, cn) + co.RouteViewCounter.Bump3(170, cn) return nil case "": // Stop the favicons, robots.txt file, etc. resolving to the topics list // TODO: Add support for favicons and robots.txt files switch(extraData) { case "robots.txt": - co.RouteViewCounter.Bump3(170, cn) + co.RouteViewCounter.Bump3(172, cn) return routes.RobotsTxt(w,req) case "favicon.ico": w = r.responseWriter(w) req.URL.Path = "/s/favicon.ico" - co.RouteViewCounter.Bump3(173, cn) + co.RouteViewCounter.Bump3(175, cn) routes.StaticFile(w,req) return nil case "opensearch.xml": - co.RouteViewCounter.Bump3(172, cn) + co.RouteViewCounter.Bump3(174, cn) return routes.OpenSearchXml(w,req) /*case "sitemap.xml": - co.RouteViewCounter.Bump3(171, cn) + co.RouteViewCounter.Bump3(173, cn) return routes.SitemapXml(w,req)*/ } co.RouteViewCounter.Bump(0) @@ -2794,10 +2816,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * if ok { // TODO: Be more specific about *which* dynamic route it is - co.RouteViewCounter.Bump(167) + co.RouteViewCounter.Bump(169) return h(w,req,user) } - co.RouteViewCounter.Bump3(174, cn) + co.RouteViewCounter.Bump3(176, cn) if !c.Config.DisableSuspLog { lp := strings.ToLower(req.URL.Path) diff --git a/install/install.go b/install/install.go index e63b067c..c0536977 100644 --- a/install/install.go +++ b/install/install.go @@ -31,18 +31,18 @@ func Lookup(name string) (InstallAdapter, bool) { func createAdmin() error { fmt.Println("Creating the admin user") - hashedPassword, salt, err := BcryptGeneratePassword("password") - if err != nil { - return err + hashedPassword, salt, e := BcryptGeneratePassword("password") + if e != nil { + return e } // Build the admin user query - adminUserStmt, err := qgen.Builder.SimpleInsert("users", "name, password, salt, email, group, is_super_admin, active, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt, message, last_ip", "'Admin',?,?,'admin@localhost',1,1,1,UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''") - if err != nil { - return err + adminUserStmt, e := qgen.Builder.SimpleInsert("users", "name, password, salt, email, group, is_super_admin, active, createdAt, lastActiveAt, lastLiked, oldestItemLikedCreatedAt, message, last_ip", "'Admin',?,?,'admin@localhost',1,1,1,UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''") + if e != nil { + return e } // Run the admin user query - _, err = adminUserStmt.Exec(hashedPassword, salt) - return err + _, e = adminUserStmt.Exec(hashedPassword, salt) + return e } diff --git a/langs/english.json b/langs/english.json index e7568894..8aeb92dc 100644 --- a/langs/english.json +++ b/langs/english.json @@ -56,7 +56,9 @@ "megapost_min_words":"Mega Post Minimum Words", "meta_desc":"Meta Description", "rapid_loading":"Rapid Loaded?", - "google_site_verify":"Google Site Verify" + "google_site_verify":"Google Site Verify", + "avatar_visibility":"Avatar Visibility", + "avatar_visibility_label":"Everyone,Member Only" }, "PermPresets": { @@ -479,14 +481,14 @@ "panel_rank_guests":"Guests", "panel_rank_members":"Members", - "panel_preset_everyone":"Everyone", - "panel_preset_announcements":"Announcements", - "panel_preset_member_only":"Member Only", - "panel_preset_staff_only":"Staff Only", - "panel_preset_admin_only":"Admin Only", - "panel_preset_archive":"Archive", - "panel_preset_custom":"Custom", - "panel_preset_public":"Public", + "panel.preset_everyone":"Everyone", + "panel.preset_announcements":"Announcements", + "panel.preset_member_only":"Member Only", + "panel.preset_staff_only":"Staff Only", + "panel.preset_admin_only":"Admin Only", + "panel.preset_archive":"Archive", + "panel.preset_custom":"Custom", + "panel.preset_public":"Public", "panel_active_hidden":"Hidden", "panel_perms_no_access":"No Access", @@ -693,7 +695,7 @@ "forum_start_one":"Start one?", "forums_head":"Forums", - "forums_no_description":"No description", + "forums_no_desc":"No description", "forums_none":"None", "forums_no_forums":"You don't have access to any forums.", @@ -944,26 +946,40 @@ "panel.forums_create_head":"Add Forum", "panel.forums_create_name_label":"Name", "panel.forums_create_name":"Super Secret Forum", - "panel.forums_create_description_label":"Description", - "panel.forums_create_description":"Where all the super secret stuff happens", + "panel.forums_create_desc_label":"Description", + "panel.forums_create_desc":"Where all the super secret stuff happens", "panel.forums_active_label":"Active", "panel.forums_preset_label":"Preset", "panel.forums_create_button":"Add Forum", "panel.forums_update_order_button":"Update Order", "panel.forums_order_updated":"The forums have been successfully updated", - "panel_forum_head_suffix":" Forum", - "panel_forum_name":"Name", - "panel_forum_name_placeholder":"General Forum", - "panel_forum_description":"Description", - "panel_forum_description_placeholder":"Where the general stuff happens", - "panel_forum_active":"Active", - "panel_forum_preset":"Preset", - "panel_forum_update_button":"Update Forum", - "panel_forum_permissions_head":"Forum Permissions", - "panel_forum_edit_button":"Edit", - "panel_forum_short_update_button":"Update", - "panel_forum_full_edit_button":"Full Edit", + "panel.forum_head_suffix":" Forum", + "panel.forum_name":"Name", + "panel.forum_name_placeholder":"General Forum", + "panel.forum_desc":"Description", + "panel.forum_desc_placeholder":"Where the general stuff happens", + "panel.forum_active":"Active", + "panel.forum_preset":"Preset", + "panel.forum_update_button":"Update Forum", + "panel.forum_permissions_head":"Forum Permissions", + "panel.forum_edit_button":"Edit", + "panel.forum_short_update_button":"Update", + "panel.forum_full_edit_button":"Full Edit", + + "panel.forum_actions_head":"Actions", + "panel.forum_actions_create_head":"Create Action", + "panel.forum_action_run_on_topic_creation":"Run on Topic Creation", + "panel.forum_action_run_days_after_topic_creation":"Run Days After Topic Creation", + "panel.forum_action_run_days_after_topic_last_reply":"Run Days After Topic Last Reply", + "panel.forum_action_action":"Action", + "panel.forum_action_action_delete":"Delete", + "panel.forum_action_action_lock":"Lock", + "panel.forum_action_action_unlock":"Unlock", + "panel.forum_action_action_move":"Move", + "panel.forum_action_extra":"Extra", + "panel.forum_action_create_button":"Create Action", + "panel_forum_delete_are_you_sure":"Are you sure you want to delete the '%s' forum?", "panel_groups_head":"Groups", @@ -1244,6 +1260,7 @@ "panel_debug_goroutine_count_label":"Goroutines", "panel_debug_cpu_count_label":"CPUs", + "panel_debug_http_conns_label":"HTTP Conns", "panel_debug_tasks":"Tasks", "panel_debug_tasks_half_second":"Half Second", diff --git a/main.go b/main.go index 7ef024c2..28abd6d3 100644 --- a/main.go +++ b/main.go @@ -143,10 +143,10 @@ func storeInit() (e error) { if e != nil { return ws(e) } - /*c.ForumActionStore, e = c.NewDefaultForumActionStore(acc) + c.ForumActionStore, e = c.NewDefaultForumActionStore(acc) if e != nil { return ws(e) - }*/ + } c.Convos, e = c.NewDefaultConversationStore(acc) if e != nil { return ws(e) @@ -549,8 +549,7 @@ func main() { // Resource Management Goroutine go func() { - uc := c.Users.GetCache() - tc := c.Topics.GetCache() + uc, tc := c.Users.GetCache(), c.Topics.GetCache() if uc == nil && tc == nil { return } @@ -630,7 +629,7 @@ func main() { func startServer() { // We might not need the timeouts, if we're behind a reverse-proxy like Nginx - newServer := func(addr string, handler http.Handler) *http.Server { + newServer := func(addr string, h http.Handler) *http.Server { rtime := c.Config.ReadTimeout if rtime == 0 { rtime = 8 @@ -650,8 +649,9 @@ func startServer() { itime = 0 } return &http.Server{ - Addr: addr, - Handler: handler, + Addr: addr, + Handler: h, + ConnState: c.ConnWatch.StateChange, ReadTimeout: time.Duration(rtime) * time.Second, WriteTimeout: time.Duration(wtime) * time.Second, diff --git a/misc_test.go b/misc_test.go index 9998527b..456dff07 100644 --- a/misc_test.go +++ b/misc_test.go @@ -1128,8 +1128,8 @@ func TestReplyStore(t *testing.T) { func testReplyStore(t *testing.T, newID int, ip string) { ex, exf := exp(t), expf(t) - replyTest2 := func(r *c.Reply, err error, rid, parentID, createdBy int, content, ip string) { - expectNilErr(t, err) + replyTest2 := func(r *c.Reply, e error, rid, parentID, createdBy int, content, ip string) { + expectNilErr(t, e) exf(r.ID == rid, "RID #%d has the wrong ID. It should be %d not %d", rid, rid, r.ID) exf(r.ParentID == parentID, "The parent topic of RID #%d should be %d not %d", rid, parentID, r.ParentID) exf(r.CreatedBy == createdBy, "The creator of RID #%d should be %d not %d", rid, createdBy, r.CreatedBy) @@ -1138,12 +1138,12 @@ func testReplyStore(t *testing.T, newID int, ip string) { } replyTest := func(rid, parentID, createdBy int, content, ip string) { - r, err := c.Rstore.Get(rid) - replyTest2(r, err, rid, parentID, createdBy, content, ip) - r, err = c.Rstore.GetCache().Get(rid) - replyTest2(r, err, rid, parentID, createdBy, content, ip) + r, e := c.Rstore.Get(rid) + replyTest2(r, e, rid, parentID, createdBy, content, ip) + r, e = c.Rstore.GetCache().Get(rid) + replyTest2(r, e, rid, parentID, createdBy, content, ip) } - replyTest(1, 1, 1, "A reply!", "::1") + replyTest(1, 1, 1, "A reply!", "") // ! This is hard to do deterministically as the system may pre-load certain items but let's give it a try: //_, err = c.Rstore.GetCache().Get(1) @@ -2279,6 +2279,108 @@ func TestWidgets(t *testing.T) { exf(len(widgets) == 0, "RightSidebar should have 0 items, not %d", len(widgets)) } +/*type ForumActionStoreInt interface { + Get(faid int) (*ForumAction, error) + GetInForum(fid int) ([]*ForumAction, error) + GetAll() ([]*ForumAction, error) + GetNewTopicActions(fid int) ([]*ForumAction, error) + + Add(fa *ForumAction) (int, error) + Delete(faid int) error + Exists(faid int) bool + Count() int + CountInForum(fid int) int + + DailyTick() error +}*/ + +func TestForumActions(t *testing.T) { + ex, exf, s := exp(t), expf(t), c.ForumActionStore + + count := s.CountInForum(-1) + exf(count == 0, "count should be %d not %d", 0, count) + count = s.CountInForum(0) + exf(count == 0, "count in 0 should be %d not %d", 0, count) + ex(!s.Exists(-1), "faid -1 should not exist") + ex(!s.Exists(0), "faid 0 should not exist") + _, e := s.Get(-1) + recordMustNotExist(t, e, "faid -1 should not exist") + _, e = s.Get(0) + recordMustNotExist(t, e, "faid 0 should not exist") + + noActions := func(fid, faid int) { + /*sfid, */ sfaid := /*strconv.Itoa(fid), */ strconv.Itoa(faid) + count := s.Count() + exf(count == 0, "count should be %d not %d", 0, count) + count = s.CountInForum(fid) + exf(count == 0, "count in %d should be %d not %d", fid, 0, count) + exf(!s.Exists(faid), "faid %d should not exist", faid) + _, e = s.Get(faid) + recordMustNotExist(t, e, "faid "+sfaid+" should not exist") + fas, e := s.GetInForum(fid) + //recordMustNotExist(t, e, "fid "+sfid+" should not have any actions") + expectNilErr(t, e) // TODO: Why does this not return ErrNoRows? + exf(len(fas) == 0, "len(fas) should be %d not %d", 0, len(fas)) + fas, e = s.GetAll() + //recordMustNotExist(t, e, "there should not be any actions") + expectNilErr(t, e) // TODO: Why does this not return ErrNoRows? + exf(len(fas) == 0, "len(fas) should be %d not %d", 0, len(fas)) + fas, e = s.GetNewTopicActions(fid) + //recordMustNotExist(t, e, "fid "+sfid+" should not have any new topic actions") + expectNilErr(t, e) // TODO: Why does this not return ErrNoRows? + exf(len(fas) == 0, "len(fas) should be %d not %d", 0, len(fas)) + } + noActions(1, 1) + + fid, e := c.Forums.Create("Forum Action Test", "Forum Action Test", true, "") + expectNilErr(t, e) + noActions(fid, 1) + + faid, e := c.ForumActionStore.Add(&c.ForumAction{ + Forum: fid, + RunOnTopicCreation: false, + RunDaysAfterTopicCreation: 1, + RunDaysAfterTopicLastReply: 0, + Action: c.ForumActionLock, + Extra: "", + }) + expectNilErr(t, e) + exf(faid == 1, "faid should be %d not %d", 1, faid) + count = s.Count() + exf(count == 1, "count should be %d not %d", 1, count) + count = s.CountInForum(fid) + exf(count == 1, "count in %d should be %d not %d", fid, 1, count) + exf(s.Exists(faid), "faid %d should exist", faid) + + fa, e := s.Get(faid) + expectNilErr(t, e) + exf(fa.ID == faid, "fa.ID should be %d not %d", faid, fa.ID) + exf(fa.Forum == fid, "fa.Forum should be %d not %d", fid, fa.Forum) + exf(fa.RunOnTopicCreation == false, "fa.RunOnTopicCreation should be false") + exf(fa.RunDaysAfterTopicCreation == 1, "fa.RunDaysAfterTopicCreation should be %d not %d", 1, fa.RunDaysAfterTopicCreation) + exf(fa.RunDaysAfterTopicLastReply == 0, "fa.RunDaysAfterTopicLastReply should be %d not %d", 0, fa.RunDaysAfterTopicLastReply) + exf(fa.Action == c.ForumActionLock, "fa.Action should be %d not %d", c.ForumActionLock, fa.Action) + exf(fa.Extra == "", "fa.Extra should be '%s' not '%s'", "", fa.Extra) + + tid, e := c.Topics.Create(fid, "Forum Action Topic", "Forum Action Topic", 1, "") + expectNilErr(t, e) + topic, e := c.Topics.Get(tid) + expectNilErr(t, e) + dayAgo := time.Now().AddDate(0, 0, -5) + expectNilErr(t, topic.TestSetCreatedAt(dayAgo)) + expectNilErr(t, fa.Run()) + topic, e = c.Topics.Get(tid) + expectNilErr(t, e) + ex(topic.IsClosed, "topic.IsClosed should be true") + /*_, e = c.Rstore.Create(topic, "Forum Action Reply", "", 1) + expectNilErr(t, e)*/ + + _ = tid + + expectNilErr(t, s.Delete(faid)) + noActions(fid, faid) +} + func TestTopicList(t *testing.T) { ex, exf := exp(t), expf(t) fid, err := c.Forums.Create("Test Forum", "Desc for test forum", true, "") diff --git a/patcher/patches.go b/patcher/patches.go index 456d5cd5..113a4d7b 100644 --- a/patcher/patches.go +++ b/patcher/patches.go @@ -53,6 +53,7 @@ func init() { addPatch(33, patch33) addPatch(34, patch34) addPatch(35, patch35) + addPatch(36, patch36) } func bcol(col string, val bool) qgen.DBTableColumn { @@ -168,18 +169,17 @@ func patch0(scanner *bufio.Scanner) (err error) { } func patch1(scanner *bufio.Scanner) error { - routes := map[string]string{ + return renameRoutes(map[string]string{ "routeAccountEditCriticalSubmit": "routes.AccountEditCriticalSubmit", "routeAccountEditAvatar": "routes.AccountEditAvatar", "routeAccountEditAvatarSubmit": "routes.AccountEditAvatarSubmit", "routeAccountEditUsername": "routes.AccountEditUsername", "routeAccountEditUsernameSubmit": "routes.AccountEditUsernameSubmit", - } - return renameRoutes(routes) + }) } func patch2(scanner *bufio.Scanner) error { - routes := map[string]string{ + return renameRoutes(map[string]string{ "routeLogout": "routes.AccountLogout", "routeShowAttachment": "routes.ShowAttachment", "routeChangeTheme": "routes.ChangeTheme", @@ -189,8 +189,7 @@ func patch2(scanner *bufio.Scanner) error { "routeDynamic": "routes.DynamicRoute", "routeUploads": "routes.UploadedFile", "BadRoute": "routes.BadRoute", - } - return renameRoutes(routes) + }) } func patch3(scanner *bufio.Scanner) error { @@ -248,14 +247,14 @@ func patch4(scanner *bufio.Scanner) error { "routePanelForumsEditPermsAdvanceSubmit": "panel.ForumsEditPermsAdvanceSubmit", "routePanelBackups": "panel.Backups", } - err := renameRoutes(routes) - if err != nil { - return err + e := renameRoutes(routes) + if e != nil { + return e } - err = execStmt(qgen.Builder.SimpleDelete("settings", "name='url_tags'")) - if err != nil { - return err + e = execStmt(qgen.Builder.SimpleDelete("settings", "name='url_tags'")) + if e != nil { + return e } return createTable("pages", "utf8mb4", "utf8mb4_general_ci", @@ -281,14 +280,14 @@ func patch5(scanner *bufio.Scanner) error { "routes.AccountEditCritical": "routes.AccountEditPassword", "routes.AccountEditCriticalSubmit": "routes.AccountEditPasswordSubmit", } - err := renameRoutes(routes) - if err != nil { - return err + e := renameRoutes(routes) + if e != nil { + return e } - err = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path='/user/edit/'", "path='/user/edit/critical/'")) - if err != nil { - return err + e = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path='/user/edit/'", "path='/user/edit/critical/'")) + if e != nil { + return e } return createTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci", @@ -333,9 +332,9 @@ func renameRoutes(routes map[string]string) error { } for key, value := range routes { - err := replaceTextWhere(key, value) - if err != nil { - return err + e := replaceTextWhere(key, value) + if e != nil { + return e } } @@ -370,9 +369,9 @@ func patch8(scanner *bufio.Scanner) error { "routePanelThemesMenuItemOrderSubmit": "panel.ThemesMenuItemOrderSubmit", "routePanelDashboard": "panel.Dashboard", } - err := renameRoutes(routes) - if err != nil { - return err + e := renameRoutes(routes) + if e != nil { + return e } return createTable("updates", "", "", @@ -812,36 +811,36 @@ func patch29(scanner *bufio.Scanner) error { } func patch30(scanner *bufio.Scanner) error { - err := execStmt(qgen.Builder.AddColumn("users_groups_promotions", tC{"registeredFor", "int", 0, false, false, "0"}, nil)) - if err != nil { - return err + e := execStmt(qgen.Builder.AddColumn("users_groups_promotions", tC{"registeredFor", "int", 0, false, false, "0"}, nil)) + if e != nil { + return e } return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", "")) } func patch31(scanner *bufio.Scanner) (e error) { addKey := func(tbl, col string, tk qgen.DBTableKey) error { - /*err := execStmt(qgen.Builder.RemoveIndex(tbl, col)) - if err != nil { - return err + /*e := execStmt(qgen.Builder.RemoveIndex(tbl, col)) + if e != nil { + return e }*/ return execStmt(qgen.Builder.AddKey(tbl, col, tk)) } - err := addKey("topics", "title", tK{"title", "fulltext", "", false}) - if err != nil { - return err + e = addKey("topics", "title", tK{"title", "fulltext", "", false}) + if e != nil { + return e } - err = addKey("topics", "content", tK{"content", "fulltext", "", false}) - if err != nil { - return err + e = addKey("topics", "content", tK{"content", "fulltext", "", false}) + if e != nil { + return e } return addKey("replies", "content", tK{"content", "fulltext", "", false}) } func createTable(tbl, charset, collation string, cols []tC, keys []tK) error { - err := execStmt(qgen.Builder.DropTable(tbl)) - if err != nil { - return err + e := execStmt(qgen.Builder.DropTable(tbl)) + if e != nil { + return e } return execStmt(qgen.Builder.CreateTable(tbl, charset, collation, cols, keys)) } @@ -934,9 +933,31 @@ func patch34(scanner *bufio.Scanner) error { } func patch35(scanner *bufio.Scanner) error { - err := execStmt(qgen.Builder.AddColumn("topics", tC{"weekEvenViews", "int", 0, false, false, "0"}, nil)) - if err != nil { - return err + e := execStmt(qgen.Builder.AddColumn("topics", tC{"weekEvenViews", "int", 0, false, false, "0"}, nil)) + if e != nil { + return e } return execStmt(qgen.Builder.AddColumn("topics", tC{"weekOddViews", "int", 0, false, false, "0"}, nil)) } + +func patch36(scanner *bufio.Scanner) error { + e := createTable("forums_actions", "utf8mb4", "utf8mb4_general_ci", + []tC{ + {"faid", "int", 0, false, true, ""}, + {"fid", "int", 0, false, false, ""}, + bcol("runOnTopicCreation", false), + {"runDaysAfterTopicCreation", "int", 0, false, false, "0"}, + {"runDaysAfterTopicLastReply", "int", 0, false, false, "0"}, + ccol("action", 50, ""), + ccol("extra", 200, "''"), + }, + []tK{ + {"faid", "primary", "", false}, + }, + ) + if e != nil { + return e + } + //qgen.Install.SimpleInsert("settings", "name, content, type, constraints", "'activation_type','1','list','1-3'") + return execStmt(qgen.Builder.SimpleInsert("settings", "name, content, type", "'avatar_visibility','0','list','0-1'")) +} diff --git a/query_gen/acc_builders.go b/query_gen/acc_builders.go index ace50565..f03ca94d 100644 --- a/query_gen/acc_builders.go +++ b/query_gen/acc_builders.go @@ -32,6 +32,11 @@ func (b *accDeleteBuilder) DateOlderThan(col string, quantity int, unit string) return b } +func (b *accDeleteBuilder) DateOlderThanQ(col, unit string) *accDeleteBuilder { + b.dateCutoff = &dateCutoff{col, 0, unit, 11} + return b +} + /*func (b *accDeleteBuilder) Prepare() *sql.Stmt { return b.build.SimpleDelete(b.table, b.where) }*/ @@ -87,6 +92,11 @@ func (b *accUpdateBuilder) DateOlderThan(col string, quantity int, unit string) return b } +func (b *accUpdateBuilder) DateOlderThanQ(col, unit string) *accUpdateBuilder { + b.up.dateCutoff = &dateCutoff{col, 0, unit, 11} + return b +} + func (b *accUpdateBuilder) WhereQ(sel *selectPrebuilder) *accUpdateBuilder { b.up.whereSubQuery = sel return b @@ -98,14 +108,24 @@ func (b *accUpdateBuilder) Prepare() *sql.Stmt { } return b.build.prepare(b.build.adapter.SimpleUpdate(b.up)) } +func (b *accUpdateBuilder) Stmt() *sql.Stmt { + if b.up.whereSubQuery != nil { + return b.build.prepare(b.build.adapter.SimpleUpdateSelect(b.up)) + } + return b.build.prepare(b.build.adapter.SimpleUpdate(b.up)) +} func (b *accUpdateBuilder) Exec(args ...interface{}) (res sql.Result, err error) { - query, err := b.build.adapter.SimpleUpdate(b.up) + q, err := b.build.adapter.SimpleUpdate(b.up) if err != nil { return res, err } - //fmt.Println("query:", query) - return b.build.exec(query, args...) + //fmt.Println("q:", q) + return b.build.exec(q, args...) +} + +type AccBuilder interface { + Prepare() *sql.Stmt } type AccSelectBuilder struct { @@ -147,8 +167,8 @@ func (b *AccSelectBuilder) In(col string, inList []int) *AccSelectBuilder { // TODO: Optimise this where := col + " IN(" - for _, item := range inList { - where += strconv.Itoa(item) + "," + for _, it := range inList { + where += strconv.Itoa(it) + "," } where = where[:len(where)-1] + ")" if b.where != "" { @@ -217,6 +237,15 @@ func (b *AccSelectBuilder) Prepare() *sql.Stmt { return b.build.SimpleSelect(b.table, b.columns, b.where, b.orderby, b.limit) } +func (b *AccSelectBuilder) Stmt() *sql.Stmt { + // TODO: Phase out the procedural API and use the adapter's OO API? The OO API might need a bit more work before we do that and it needs to be rolled out to MSSQL. + if b.dateCutoff != nil || b.inChain != nil { + selectBuilder := b.build.GetAdapter().Builder().Select().FromAcc(b) + return b.build.prepare(b.build.GetAdapter().ComplexSelect(selectBuilder)) + } + return b.build.SimpleSelect(b.table, b.columns, b.where, b.orderby, b.limit) +} + func (b *AccSelectBuilder) ComplexPrepare() *sql.Stmt { selectBuilder := b.build.GetAdapter().Builder().Select().FromAcc(b) return b.build.prepare(b.build.GetAdapter().ComplexSelect(selectBuilder)) diff --git a/router_gen/routes.go b/router_gen/routes.go index 6f6ded63..7f6d8fd5 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -199,6 +199,8 @@ func panelRoutes() *RouteGroup { Action("panel.ForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"), View("panel.ForumsEditPermsAdvance", "/panel/forums/edit/perms/", "extraData"), Action("panel.ForumsEditPermsAdvanceSubmit", "/panel/forums/edit/perms/adv/submit/", "extraData"), + Action("panel.ForumsEditActionCreateSubmit", "/panel/forums/action/create/submit/", "extraData"), + Action("panel.ForumsEditActionDeleteSubmit", "/panel/forums/action/delete/submit/", "extraData"), View("panel.Settings", "/panel/settings/"), View("panel.SettingEdit", "/panel/settings/edit/", "extraData"), diff --git a/routes/panel/debug.go b/routes/panel/debug.go index 0317516d..0ee1c113 100644 --- a/routes/panel/debug.go +++ b/routes/panel/debug.go @@ -18,10 +18,10 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { goVersion := runtime.Version() dbVersion := qgen.Builder.DbVersion() - upDuration := time.Since(c.StartTime) - hours := int(upDuration.Hours()) - mins := int(upDuration.Minutes()) - secs := int(upDuration.Seconds()) + upDur := time.Since(c.StartTime) + hours := int(upDur.Hours()) + mins := int(upDur.Minutes()) + secs := int(upDur.Seconds()) var uptime string if hours > 24 { days := hours / 24 @@ -44,6 +44,7 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { // TODO: Fetch the adapter from Builder rather than getting it from a global? goroutines := runtime.NumGoroutine() cpus := runtime.NumCPU() + httpConns := c.ConnWatch.Count() debugTasks := c.DebugPageTasks{c.ScheduledHalfSecondTaskCount(), c.ScheduledSecondTaskCount(), c.ScheduledFifteenMinuteTaskCount(), c.ScheduledHourTaskCount(), c.ShutdownTaskCount()} var memStats runtime.MemStats @@ -51,20 +52,17 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { var tlen, ulen, rlen int var tcap, ucap, rcap int - tcache := c.Topics.GetCache() - if tcache != nil { - tlen = tcache.Length() - tcap = tcache.GetCapacity() + tc := c.Topics.GetCache() + if tc != nil { + tlen, tcap = tc.Length(), tc.GetCapacity() } - ucache := c.Users.GetCache() - if ucache != nil { - ulen = ucache.Length() - ucap = ucache.GetCapacity() + uc := c.Users.GetCache() + if uc != nil { + ulen, ucap = uc.Length(), uc.GetCapacity() } - rcache := c.Rstore.GetCache() - if rcache != nil { - rlen = rcache.Length() - rcap = rcache.GetCapacity() + rc := c.Rstore.GetCache() + if rc != nil { + rlen, rcap = rc.Length(), rc.GetCapacity() } topicListThawed := c.TopicListThaw.Thawed() @@ -121,12 +119,13 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { if fErr != nil { return c.InternalError(fErr, w, r) } + // TODO: How can we measure this without freezing up the entire page? //gitSize, _ := c.DirSize("./.git") gitSize := 0 debugDisk := c.DebugPageDisk{staticSize, attachSize, uploadsSize, logsSize, backupsSize, gitSize} - pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, debugTasks, memStats, debugCache, debugDatabase, debugDisk} + pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, httpConns, debugTasks, memStats, debugCache, debugDatabase, debugDisk} return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug", pi}) } diff --git a/routes/panel/forums.go b/routes/panel/forums.go index a4836199..2169072d 100644 --- a/routes/panel/forums.go +++ b/routes/panel/forums.go @@ -144,6 +144,7 @@ func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.Rout if ferr != nil { return ferr } + // TODO: Move this even earlier? js := r.PostFormValue("js") == "1" if !u.Perms.ManageForums { return c.NoPermissionsJSQ(w, r, u, js) @@ -187,14 +188,14 @@ func ForumsEdit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) } basePage.Header.AddScriptAsync("panel_forum_edit.js") - forum, err := c.Forums.Get(fid) + f, err := c.Forums.Get(fid) if err == sql.ErrNoRows { return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u) } else if err != nil { return c.InternalError(err, w, r) } - if forum.Preset == "" { - forum.Preset = "custom" + if f.Preset == "" { + f.Preset = "custom" } glist, err := c.Groups.GetAll() @@ -221,7 +222,16 @@ func ForumsEdit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) basePage.AddNotice("panel_forum_updated") } - pi := c.PanelEditForumPage{basePage, forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist} + falist, e := c.ForumActionStore.GetInForum(f.ID) + if err != sql.ErrNoRows && e != nil { + return c.InternalError(e, w, r) + } + afalist := make([]*c.ForumActionAction, len(falist)) + for i, faitem := range falist { + afalist[i] = &c.ForumActionAction{faitem, c.ConvActToString(faitem.Action)} + } + + pi := c.PanelEditForumPage{basePage, f.ID, f.Name, f.Desc, f.Active, f.Preset, gplist, afalist} return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forum_edit", &pi}) } @@ -290,7 +300,7 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sf return c.LocalErrorJSQ("Invalid Group ID", w, r, u, js) } - forum, err := c.Forums.Get(fid) + f, err := c.Forums.Get(fid) if err == sql.ErrNoRows { return c.LocalErrorJSQ("This forum doesn't exist", w, r, u, js) } else if err != nil { @@ -298,7 +308,7 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sf } permPreset := c.StripInvalidGroupForumPreset(r.PostFormValue("perm_preset")) - err = forum.SetPreset(permPreset, gid) + err = f.SetPreset(permPreset, gid) if err != nil { return c.LocalErrorJSQ(err.Error(), w, r, u, js) } @@ -311,23 +321,20 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sf } // A helper function for the Advanced portion of the Forum Perms Editor -func forumPermsExtractDash(paramList string) (fid, gid int, err error) { +func forumPermsExtractDash(paramList string) (fid, gid int, e error) { params := strings.Split(paramList, "-") if len(params) != 2 { return fid, gid, errors.New("Parameter count mismatch") } - - fid, err = strconv.Atoi(params[0]) - if err != nil { + fid, e = strconv.Atoi(params[0]) + if e != nil { return fid, gid, errors.New("The provided Forum ID is not a valid number.") } - - gid, err = strconv.Atoi(params[1]) - if err != nil { - err = errors.New("The provided Group ID is not a valid number.") + gid, e = strconv.Atoi(params[1]) + if e != nil { + e = errors.New("The provided Group ID is not a valid number.") } - - return fid, gid, err + return fid, gid, e } func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, u *c.User, paramList string) c.RouteError { @@ -403,7 +410,7 @@ func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, u *c.U return c.LocalError(err.Error(), w, r, u) } - forum, err := c.Forums.Get(fid) + f, err := c.Forums.Get(fid) if err == sql.ErrNoRows { return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u) } else if err != nil { @@ -417,25 +424,24 @@ func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, u *c.U return c.InternalError(err, w, r) } - extractPerm := func(name string) bool { + ep := func(name string) bool { pvalue := r.PostFormValue("perm-" + name) return (pvalue == "1") } - // TODO: Generate this code? - fp.ViewTopic = extractPerm("ViewTopic") - fp.LikeItem = extractPerm("LikeItem") - fp.CreateTopic = extractPerm("CreateTopic") - fp.EditTopic = extractPerm("EditTopic") - fp.DeleteTopic = extractPerm("DeleteTopic") - fp.CreateReply = extractPerm("CreateReply") - fp.EditReply = extractPerm("EditReply") - fp.DeleteReply = extractPerm("DeleteReply") - fp.PinTopic = extractPerm("PinTopic") - fp.CloseTopic = extractPerm("CloseTopic") - fp.MoveTopic = extractPerm("MoveTopic") + fp.ViewTopic = ep("ViewTopic") + fp.LikeItem = ep("LikeItem") + fp.CreateTopic = ep("CreateTopic") + fp.EditTopic = ep("EditTopic") + fp.DeleteTopic = ep("DeleteTopic") + fp.CreateReply = ep("CreateReply") + fp.EditReply = ep("EditReply") + fp.DeleteReply = ep("DeleteReply") + fp.PinTopic = ep("PinTopic") + fp.CloseTopic = ep("CloseTopic") + fp.MoveTopic = ep("MoveTopic") - err = forum.SetPerms(&fp, "custom", gid) + err = f.SetPerms(&fp, "custom", gid) if err != nil { return c.LocalErrorJSQ(err.Error(), w, r, u, js) } @@ -446,3 +452,107 @@ func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, u *c.U return successRedirect("/panel/forums/edit/perms/"+strconv.Itoa(fid)+"-"+strconv.Itoa(gid)+"?updated=1", w, r, js) } + +func ForumsEditActionDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfaid string) c.RouteError { + _, ferr := c.SimplePanelUserCheck(w, r, u) + if ferr != nil { + return ferr + } + // TODO: Should we split this permission? + if !u.Perms.ManageForums { + return c.NoPermissions(w, r, u) + } + js := r.PostFormValue("js") == "1" + + faid, e := strconv.Atoi(sfaid) + if e != nil { + return c.LocalError("The forum action ID is not a valid integer.", w, r, u) + } + e = c.ForumActionStore.Delete(faid) + if e != nil { + return c.InternalError(e, w, r) + } + + fid, e := strconv.Atoi(r.FormValue("ret")) + if e != nil { + return c.LocalError("The forum action ID is not a valid integer.", w, r, u) + } + if !c.Forums.Exists(fid) { + return c.LocalError("The target forum doesn't exist.", w, r, u) + } + + return successRedirect("/panel/forums/edit/"+strconv.Itoa(fid)+"?updated=1", w, r, js) +} + +func ForumsEditActionCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError { + _, ferr := c.SimplePanelUserCheck(w, r, u) + if ferr != nil { + return ferr + } + // TODO: Should we split this permission? + if !u.Perms.ManageForums { + return c.NoPermissions(w, r, u) + } + js := r.PostFormValue("js") == "1" + + fid, e := strconv.Atoi(sfid) + if e != nil { + return c.LocalError("The provided Forum ID is not a valid number.", w, r, u) + } + if !c.Forums.Exists(fid) { + return c.LocalError("This forum does not exist", w, r, u) + } + + runOnTopicCreation := r.PostFormValue("action_run_on_topic_creation") == "1" + + f := func(s string) (int, c.RouteError) { + i, e := strconv.Atoi(r.PostFormValue(s)) + if e != nil { + return i, c.LocalError(s+" is not a valid integer.", w, r, u) + } + if i < 0 { + return i, c.LocalError(s+" cannot be less than 0", w, r, u) + } + return i, nil + } + runDaysAfterTopicCreation, re := f("action_run_days_after_topic_creation") + if re != nil { + return re + } + runDaysAfterTopicLastReply, re := f("action_run_days_after_topic_last_reply") + if re != nil { + return re + } + + action := r.PostFormValue("action_action") + aint := c.ConvStringToAct(action) + if aint == -1 { + return c.LocalError("invalid action", w, r, u) + } + + extra := r.PostFormValue("action_extra") + switch aint { + case c.ForumActionMove: + conv, e := strconv.Atoi(extra) + if e != nil { + return c.LocalError("action_extra is not a valid integer.", w, r, u) + } + extra = strconv.Itoa(conv) + default: + extra = "" + } + + _, e = c.ForumActionStore.Add(&c.ForumAction{ + Forum: fid, + RunOnTopicCreation: runOnTopicCreation, + RunDaysAfterTopicCreation: runDaysAfterTopicCreation, + RunDaysAfterTopicLastReply: runDaysAfterTopicLastReply, + Action: aint, + Extra: extra, + }) + if e != nil { + return c.InternalError(e, w, r) + } + + return successRedirect("/panel/forums/edit/"+strconv.Itoa(fid)+"?updated=1", w, r, js) +} diff --git a/routes/panel/word_filters.go b/routes/panel/word_filters.go index c6643898..ecf4e2c1 100644 --- a/routes/panel/word_filters.go +++ b/routes/panel/word_filters.go @@ -20,9 +20,9 @@ func WordFilters(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError } // TODO: What if this list gets too long? - filters, err := c.WordFilters.GetAll() - if err != nil { - return c.InternalError(err, w, r) + filters, e := c.WordFilters.GetAll() + if e != nil { + return c.InternalError(e, w, r) } pi := c.PanelPage{basePage, tList, filters} @@ -48,13 +48,13 @@ func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement replace := strings.TrimSpace(r.PostFormValue("replace")) - wfid, err := c.WordFilters.Create(find, replace) - if err != nil { - return c.InternalErrorJSQ(err, w, r, js) + wfid, e := c.WordFilters.Create(find, replace) + if e != nil { + return c.InternalErrorJSQ(e, w, r, js) } - err = c.AdminLogs.Create("create", wfid, "word_filter", u.GetIP(), u.ID) - if err != nil { - return c.InternalError(err, w, r) + e = c.AdminLogs.Create("create", wfid, "word_filter", u.GetIP(), u.ID) + if e != nil { + return c.InternalError(e, w, r) } return successRedirect("/panel/settings/word-filters/", w, r, js) diff --git a/routes/topic_list.go b/routes/topic_list.go index bcaa2b82..e6f41b6c 100644 --- a/routes/topic_list.go +++ b/routes/topic_list.go @@ -13,8 +13,8 @@ import ( func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList { wsTopicList := make([]*c.WsTopicsRow, len(topicList)) - for i, topicRow := range topicList { - wsTopicList[i] = topicRow.WebSockets() + for i, tr := range topicList { + wsTopicList[i] = tr.WebSockets() } return &c.WsTopicList{wsTopicList, lastPage, 0} } diff --git a/schema/mssql/inserts.sql b/schema/mssql/inserts.sql index c33626c6..079db3a5 100644 --- a/schema/mssql/inserts.sql +++ b/schema/mssql/inserts.sql @@ -5,6 +5,7 @@ INSERT INTO [settings] ([name],[content],[type]) VALUES ('megapost_min_words','1 INSERT INTO [settings] ([name],[content],[type]) VALUES ('meta_desc','','html-attribute'); INSERT INTO [settings] ([name],[content],[type]) VALUES ('rapid_loading','1','bool'); INSERT INTO [settings] ([name],[content],[type]) VALUES ('google_site_verify','','html-attribute'); +INSERT INTO [settings] ([name],[content],[type],[constraints]) VALUES ('avatar_visibility','0','list','0-1'); INSERT INTO [themes] ([uname],[default]) VALUES ('cosora',1); INSERT INTO [emails] ([email],[uid],[validated]) VALUES ('admin@localhost',1,1); INSERT INTO [users_groups] ([name],[permissions],[plugin_perms],[is_mod],[is_admin],[is_banned],[tag]) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"UseConvosOnlyWithMod":true,"CreateProfileReply":true,"AutoEmbed":true,"AutoLink":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin'); @@ -27,8 +28,8 @@ INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (3,2,'{"View INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (4,2,'{"ViewTopic":true}'); INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (5,2,'{"ViewTopic":true}'); INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (6,2,'{"ViewTopic":true}'); -INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ip]) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1'); -INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ip]) VALUES (1,'A reply!','A reply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1'); +INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ip]) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',GETUTCDATE(),GETUTCDATE(),1,1,2,''); +INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ip]) VALUES (1,'A reply!','A reply!',GETUTCDATE(),1,GETUTCDATE(),0,0,''); INSERT INTO [menus] () VALUES (); INSERT INTO [menu_items] ([mid],[name],[htmlID],[position],[path],[aria],[tooltip],[order]) VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0); INSERT INTO [menu_items] ([mid],[name],[htmlID],[cssClass],[position],[path],[aria],[tooltip],[order]) VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1); diff --git a/schema/mysql/inserts.sql b/schema/mysql/inserts.sql index 5c56ac2f..0a340574 100644 --- a/schema/mysql/inserts.sql +++ b/schema/mysql/inserts.sql @@ -13,6 +13,7 @@ INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('megapost_min_words','10 INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('meta_desc','','html-attribute'); INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('rapid_loading','1','bool'); INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('google_site_verify','','html-attribute'); +INSERT INTO `settings`(`name`,`content`,`type`,`constraints`) VALUES ('avatar_visibility','0','list','0-1'); INSERT INTO `themes`(`uname`,`default`) VALUES ('cosora',1); INSERT INTO `emails`(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1); INSERT INTO `users_groups`(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`is_banned`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"UseConvosOnlyWithMod":true,"CreateProfileReply":true,"AutoEmbed":true,"AutoLink":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin'); @@ -35,8 +36,8 @@ INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (3,2,'{"ViewT INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (4,2,'{"ViewTopic":true}'); INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (5,2,'{"ViewTopic":true}'); INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (6,2,'{"ViewTopic":true}'); -INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ip`) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'); -INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ip`) VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'); +INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ip`) VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,''); +INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ip`) VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,''); INSERT INTO `menus`() VALUES (); INSERT INTO `menu_items`(`mid`,`name`,`htmlID`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0); INSERT INTO `menu_items`(`mid`,`name`,`htmlID`,`cssClass`,`position`,`path`,`aria`,`tooltip`,`order`) VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1); diff --git a/schema/mysql/query_forums_actions.sql b/schema/mysql/query_forums_actions.sql new file mode 100644 index 00000000..bdc863fd --- /dev/null +++ b/schema/mysql/query_forums_actions.sql @@ -0,0 +1,10 @@ +CREATE TABLE `forums_actions` ( + `faid` int not null AUTO_INCREMENT, + `fid` int not null, + `runOnTopicCreation` boolean DEFAULT 0 not null, + `runDaysAfterTopicCreation` int DEFAULT 0 not null, + `runDaysAfterTopicLastReply` int DEFAULT 0 not null, + `action` varchar(50) not null, + `extra` varchar(200) DEFAULT '' not null, + primary key(`faid`) +) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/pgsql/inserts.sql b/schema/pgsql/inserts.sql index 90ae683a..93176c77 100644 --- a/schema/pgsql/inserts.sql +++ b/schema/pgsql/inserts.sql @@ -5,6 +5,7 @@ INSERT INTO "settings"("name","content","type") VALUES ('megapost_min_words','10 INSERT INTO "settings"("name","content","type") VALUES ('meta_desc','','html-attribute'); INSERT INTO "settings"("name","content","type") VALUES ('rapid_loading','1','bool'); INSERT INTO "settings"("name","content","type") VALUES ('google_site_verify','','html-attribute'); +INSERT INTO "settings"("name","content","type","constraints") VALUES ('avatar_visibility','0','list','0-1'); INSERT INTO "themes"("uname","default") VALUES ('cosora',1); INSERT INTO "emails"("email","uid","validated") VALUES ('admin@localhost',1,1); INSERT INTO "users_groups"("name","permissions","plugin_perms","is_mod","is_admin","is_banned","tag") VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"UploadAvatars":true,"UseConvos":true,"UseConvosOnlyWithMod":true,"CreateProfileReply":true,"AutoEmbed":true,"AutoLink":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true,"MoveTopic":true}','{}',1,1,0,'Admin'); @@ -27,8 +28,8 @@ INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (3,2,'{"ViewT INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (4,2,'{"ViewTopic":true}'); INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (5,2,'{"ViewTopic":true}'); INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (6,2,'{"ViewTopic":true}'); -INSERT INTO "topics"("title","content","parsed_content","createdAt","lastReplyAt","lastReplyBy","createdBy","parentID","ip") VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'); -INSERT INTO "replies"("tid","content","parsed_content","createdAt","createdBy","lastUpdated","lastEdit","lastEditBy","ip") VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'); +INSERT INTO "topics"("title","content","parsed_content","createdAt","lastReplyAt","lastReplyBy","createdBy","parentID","ip") VALUES ('Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,''); +INSERT INTO "replies"("tid","content","parsed_content","createdAt","createdBy","lastUpdated","lastEdit","lastEditBy","ip") VALUES (1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,''); INSERT INTO "menus"() VALUES (); INSERT INTO "menu_items"("mid","name","htmlID","position","path","aria","tooltip","order") VALUES (1,'{lang.menu_forums}','menu_forums','left','/forums/','{lang.menu_forums_aria}','{lang.menu_forums_tooltip}',0); INSERT INTO "menu_items"("mid","name","htmlID","cssClass","position","path","aria","tooltip","order") VALUES (1,'{lang.menu_topics}','menu_topics','menu_topics','left','/topics/','{lang.menu_topics_aria}','{lang.menu_topics_tooltip}',1); diff --git a/schema/pgsql/query_users.sql b/schema/pgsql/query_users.sql index bf3c913c..1287bdce 100644 --- a/schema/pgsql/query_users.sql +++ b/schema/pgsql/query_users.sql @@ -10,10 +10,12 @@ CREATE TABLE "users" ( `lastActiveAt` timestamp not null, `session` varchar (200) DEFAULT '' not null, `last_ip` varchar (200) DEFAULT '' not null, + `profile_comments` int DEFAULT 0 not null, + `who_can_convo` int DEFAULT 0 not null, `enable_embeds` int DEFAULT -1 not null, `email` varchar (200) DEFAULT '' not null, `avatar` varchar (100) DEFAULT '' not null, - `message` text DEFAULT '' not null, + `message` text not null, `url_prefix` varchar (20) DEFAULT '' not null, `url_name` varchar (100) DEFAULT '' not null, `level` smallint DEFAULT 0 not null, diff --git a/schema/pgsql/query_widgets.sql b/schema/pgsql/query_widgets.sql index 87affb37..29ccc7a9 100644 --- a/schema/pgsql/query_widgets.sql +++ b/schema/pgsql/query_widgets.sql @@ -5,6 +5,6 @@ CREATE TABLE "widgets" ( `type` varchar (100) not null, `active` boolean DEFAULT 0 not null, `location` varchar (100) not null, - `data` text DEFAULT '' not null, + `data` text not null, primary key(`wid`) ); \ No newline at end of file diff --git a/templates/forums.html b/templates/forums.html index ff27af64..8e2cfefd 100644 --- a/templates/forums.html +++ b/templates/forums.html @@ -12,7 +12,7 @@ {{if .Desc}} {{.Desc}} {{else}} - {{lang "forums_no_description"}} + {{lang "forums_no_desc"}} {{end}} diff --git a/templates/panel_debug.html b/templates/panel_debug.html index 95bcfcf5..4b14c4ae 100644 --- a/templates/panel_debug.html +++ b/templates/panel_debug.html @@ -13,16 +13,16 @@ {{template "panel_debug_stat_head.html" "panel_debug_adapter_label"}} {{/** TODO: Use this for active database connections when Go 1.11 lands **/}} {{template "panel_debug_stat_head_q.html"}} - {{template "panel_debug_stat.html" .OpenConns}} + {{template "panel_debug_stat.html" .DBConns}} {{template "panel_debug_stat.html" .DBAdapter}} {{template "panel_debug_stat_q.html"}} {{template "panel_debug_stat_head.html" "panel_debug_goroutine_count_label"}} {{template "panel_debug_stat_head.html" "panel_debug_cpu_count_label"}} - {{template "panel_debug_stat_head_q.html"}} + {{template "panel_debug_stat_head.html" "panel_debug_http_conns_label"}} {{template "panel_debug_stat.html" .Goroutines}} {{template "panel_debug_stat.html" .CPUs}} - {{template "panel_debug_stat_q.html"}} + {{template "panel_debug_stat.html" .HttpConns}} {{template "panel_debug_subhead.html" "panel_debug_tasks"}}
diff --git a/templates/panel_forum_edit.html b/templates/panel_forum_edit.html index a933bfc8..775f13f2 100644 --- a/templates/panel_forum_edit.html +++ b/templates/panel_forum_edit.html @@ -1,45 +1,45 @@
-

{{.Name}}{{lang "panel_forum_head_suffix"}}

+

{{.Name}}{{lang "panel.forum_head_suffix"}}

- +
- +
-
+
-

{{lang "panel_forum_permissions_head"}}

+

{{lang "panel.forum_permissions_head"}}

{{end}} +
+{{if .Actions}} +
+

{{lang "panel.forum_actions_head"}}

+
+ +{{end}} +
+
+

{{lang "panel.forum_actions_create_head"}}

+
+
+
+
+ + + +
+ +
+ +
+
+ +
+
+
+
\ No newline at end of file diff --git a/templates/panel_forum_edit_perms.html b/templates/panel_forum_edit_perms.html index 907a9e96..55b4990d 100644 --- a/templates/panel_forum_edit_perms.html +++ b/templates/panel_forum_edit_perms.html @@ -1,5 +1,5 @@
-

{{.Name}}{{lang "panel_forum_head_suffix"}}

+

{{.Name}}{{lang "panel.forum_head_suffix"}}

@@ -16,7 +16,7 @@
{{end}}
-
+
\ No newline at end of file diff --git a/templates/panel_forums.html b/templates/panel_forums.html index a6b36359..b028c124 100644 --- a/templates/panel_forums.html +++ b/templates/panel_forums.html @@ -40,8 +40,8 @@
@@ -53,13 +53,13 @@