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.
This commit is contained in:
Azareal 2021-04-08 00:23:11 +10:00
parent 943a684332
commit c60118e7c4
44 changed files with 1793 additions and 993 deletions

View File

@ -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("router_end", "w http.ResponseWriter,r *http.Request,u *User,prefix string,extraData string")
vhooknoret("topic_reply_row_assign", "r *ReplyUser") vhooknoret("topic_reply_row_assign", "r *ReplyUser")
vhooknoret("counters_perf_tick_row", "low int64,high int64,avg int64")
//forums_frow_assign //forums_frow_assign
//Hook(name string, data interface{}) interface{} //Hook(name string, data interface{}) interface{}
/*hook := func(name, params, ret, pure string) { /*hook := func(name, params, ret, pure string) {

View File

@ -5,12 +5,12 @@ package main // import "github.com/Azareal/Gosora/hook_gen"
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"runtime/debug" "runtime/debug"
"strings"
_ "github.com/Azareal/Gosora/extend"
c "github.com/Azareal/Gosora/common"
h "github.com/Azareal/Gosora/cmd/common_hook_gen" 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 // TODO: Make sure all the errors in this file propagate upwards properly
@ -48,18 +48,19 @@ func main() {
imports := []string{"net/http"} 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) { add := func(name, params, ret, htype string, multiHook, skip bool, defaultRet, pure string) {
var params2 string
first := true first := true
for _, param := range strings.Split(params, ",") { for _, param := range strings.Split(params, ",") {
if !first { if !first {
params2 += "," params2sb.WriteRune(',')
} }
pspl := strings.Split(strings.ReplaceAll(strings.TrimSpace(param), " ", " "), " ") pspl := strings.Split(strings.ReplaceAll(strings.TrimSpace(param), " ", " "), " ")
params2 += pspl[0] params2sb.WriteString(pspl[0])
first = false 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.AddHooks(add)

View File

@ -90,16 +90,16 @@ func (s *DefaultActivityStream) AidsByParams(event string, elementID int, elemen
return aids, rows.Err() return aids, rows.Err()
} }
func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, elementType, extra string) (aids []int, err error) { func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, elementType, extra string) (aids []int, e error) {
rows, err := s.aidsByParamsExtra.Query(event, elementID, elementType, extra) rows, e := s.aidsByParamsExtra.Query(event, elementID, elementType, extra)
if err != nil { if e != nil {
return nil, err return nil, e
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var aid int var aid int
if err := rows.Scan(&aid); err != nil { if e := rows.Scan(&aid); e != nil {
return nil, err return nil, e
} }
aids = append(aids, aid) aids = append(aids, aid)
} }
@ -109,9 +109,9 @@ func (s *DefaultActivityStream) AidsByParamsExtra(event string, elementID int, e
// TODO: Write a test for this // TODO: Write a test for this
// Count returns the total number of activity stream items // Count returns the total number of activity stream items
func (s *DefaultActivityStream) Count() (count int) { func (s *DefaultActivityStream) Count() (count int) {
err := s.count.QueryRow().Scan(&count) e := s.count.QueryRow().Scan(&count)
if err != nil { if e != nil {
LogError(err) LogError(e)
} }
return count return count
} }

View File

@ -10,6 +10,8 @@ import (
"database/sql" "database/sql"
"io" "io"
"log" "log"
"net"
"net/http"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -291,3 +293,22 @@ func inqbuildstr(strs []string) ([]interface{}, string) {
} }
return idList, sb.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))
}

View File

@ -85,25 +85,19 @@ func (co *Conversation) Posts(offset, itemsPerPage int) (posts []*ConversationPo
} }
func (co *Conversation) PostsCount() (count int) { func (co *Conversation) PostsCount() (count int) {
err := convoStmts.countPosts.QueryRow(co.ID).Scan(&count) return Countf(convoStmts.countPosts, co.ID)
if err != nil {
LogError(err)
}
return count
} }
func (co *Conversation) Uids() (ids []int, err error) { func (co *Conversation) Uids() (ids []int, err error) {
rows, err := convoStmts.getUsers.Query(co.ID) rows, e := convoStmts.getUsers.Query(co.ID)
if err != nil { if e != nil {
return nil, err return nil, e
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var id int var id int
err := rows.Scan(&id) if e := rows.Scan(&id); e != nil {
if err != nil { return nil, e
return nil, err
} }
ids = append(ids, id) ids = append(ids, id)
} }
@ -111,12 +105,7 @@ func (co *Conversation) Uids() (ids []int, err error) {
} }
func (co *Conversation) Has(uid int) (in bool) { func (co *Conversation) Has(uid int) (in bool) {
var count int return Countf(convoStmts.has, uid, co.ID) > 0
err := convoStmts.has.QueryRow(uid, co.ID).Scan(&count)
if err != nil {
LogError(err)
}
return count > 0
} }
func (co *Conversation) Update() error { func (co *Conversation) Update() error {
@ -247,7 +236,7 @@ func (s *DefaultConversationStore) GetUserExtra(uid, offset int) (cos []*Convers
cmap[co.ID] = &ConversationExtra{co, nil} 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 var q string
idList := make([]interface{}, len(raw)) idList := make([]interface{}, len(raw))
for i, co := range raw { for i, co := range raw {

View File

@ -51,21 +51,22 @@ func (co *DefaultPerfCounter) Tick() error {
b.Unlock() b.Unlock()
return c return c
} }
for _, b := range co.buckets {
var low int64 var low int64
hTbl := c.GetHookTable()
for _, b := range co.buckets {
b.low.Lock() b.low.Lock()
low = b.low.counter low, b.low.counter = b.low.counter, math.MaxInt64
b.low.counter = math.MaxInt64
b.low.Unlock() b.low.Unlock()
if low == math.MaxInt64 { if low == math.MaxInt64 {
low = 0 low = 0
} }
high := getCounter(b.high) high := getCounter(b.high)
avg := getCounter(b.avg) 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? e := co.insertChunk(low, high, avg) // TODO: Bulk insert for speed?
if err != nil { if e != nil {
return errors.Wrap(errors.WithStack(err), "perf counter") return errors.Wrap(errors.WithStack(e), "perf counter")
} }
} }
return nil return nil
@ -76,8 +77,8 @@ func (co *DefaultPerfCounter) insertChunk(low, high, avg int64) error {
return nil return nil
} }
c.DebugLogf("Inserting a pchunk with low %d, high %d, avg %d", low, high, avg) c.DebugLogf("Inserting a pchunk with low %d, high %d, avg %d", low, high, avg)
_, err := co.insert.Exec(low, high, avg) _, e := co.insert.Exec(low, high, avg)
return err return e
} }
func (co *DefaultPerfCounter) Push(dur time.Duration /*,_ bool*/) { func (co *DefaultPerfCounter) Push(dur time.Duration /*,_ bool*/) {

View File

@ -126,6 +126,8 @@ var hookTable = &HookTable{
"tasks_tick_topic_list": nil, "tasks_tick_topic_list": nil,
"tasks_tick_widget_wol": nil, "tasks_tick_widget_wol": nil,
"counters_perf_tick_row": nil,
}, },
map[string][]func(string) string{ map[string][]func(string) string{
"preparse_preassign": nil, "preparse_preassign": nil,

399
common/forum_actions.go Normal file
View File

@ -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)
}
*/

View File

@ -52,9 +52,9 @@ func (s *MemoryForumPermsStore) Init() error {
// TODO: Optimise this? // TODO: Optimise this?
func (s *MemoryForumPermsStore) ReloadAll() error { func (s *MemoryForumPermsStore) ReloadAll() error {
DebugLog("Reloading the forum perms") DebugLog("Reloading the forum perms")
fids, err := Forums.GetAllIDs() fids, e := Forums.GetAllIDs()
if err != nil { if e != nil {
return err return e
} }
for _, fid := range fids { for _, fid := range fids {
if e := s.reload(fid); e != nil { if e := s.reload(fid); e != nil {
@ -68,13 +68,13 @@ func (s *MemoryForumPermsStore) ReloadAll() error {
return nil 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)) DebugDetail("perms: ", string(perms))
pperms = BlankForumPerms() pperms = BlankForumPerms()
err = json.Unmarshal(perms, &pperms) e = json.Unmarshal(perms, &pperms)
pperms.ExtData = make(map[string]bool) pperms.ExtData = make(map[string]bool)
pperms.Overrides = true pperms.Overrides = true
return pperms, err return pperms, e
} }
func (s *MemoryForumPermsStore) Reload(fid int) error { 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: Check if the forum exists?
// TODO: Fix the races // TODO: Fix the races
func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fp ForumPerms, err error) { func (s *MemoryForumPermsStore) GetCopy(fid, gid int) (fp ForumPerms, e error) {
fPermsPtr, err := s.Get(fid, gid) fPermsPtr, e := s.Get(fid, gid)
if err != nil { if e != nil {
return fp, err return fp, e
} }
return *fPermsPtr, nil return *fPermsPtr, nil
} }

View File

@ -81,18 +81,21 @@ type MemoryForumStore struct {
func NewMemoryForumStore() (*MemoryForumStore, error) { func NewMemoryForumStore() (*MemoryForumStore, error) {
acc := qgen.NewAcc() acc := qgen.NewAcc()
f := "forums" f := "forums"
set := func(s string) *sql.Stmt {
return acc.Update(f).Set(s).Where("fid=?").Prepare()
}
// TODO: Do a proper delete // TODO: Do a proper delete
return &MemoryForumStore{ return &MemoryForumStore{
get: acc.Select(f).Columns("name, desc, tmpl, active, order, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid=?").Prepare(), 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(), 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(), delete: set("name='',active=0"),
create: acc.Insert(f).Columns("name,desc,tmpl,active,preset").Fields("?,?,'',?,?").Prepare(), create: acc.Insert(f).Columns("name,desc,tmpl,active,preset").Fields("?,?,'',?,?").Prepare(),
count: acc.Count(f).Where("name != ''").Prepare(), count: acc.Count(f).Where("name != ''").Prepare(),
updateCache: acc.Update(f).Set("lastTopicID=?, lastReplyerID=?").Where("fid=?").Prepare(), updateCache: set("lastTopicID=?,lastReplyerID=?"),
addTopics: acc.Update(f).Set("topicCount=topicCount+?").Where("fid=?").Prepare(), addTopics: set("topicCount=topicCount+?"),
removeTopics: acc.Update(f).Set("topicCount=topicCount-?").Where("fid=?").Prepare(), removeTopics: set("topicCount=topicCount-?"),
lastTopic: acc.Select("topics").Columns("tid").Where("parentID=?").Orderby("lastReplyAt DESC,createdAt DESC").Limit("1").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(), updateOrder: set("order=?"),
}, acc.FirstError() }, acc.FirstError()
} }

View File

@ -44,11 +44,13 @@ var groupStmts GroupStmts
func init() { func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { 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{ groupStmts = GroupStmts{
updateGroup: acc.Update(ug).Set("name=?,tag=?").Where("gid=?").Prepare(), updateGroup: set("name=?,tag=?"),
updateGroupRank: acc.Update(ug).Set("is_admin=?,is_mod=?,is_banned=?").Where("gid=?").Prepare(), updateGroupRank: set("is_admin=?,is_mod=?,is_banned=?"),
updateGroupPerms: acc.Update(ug).Set("permissions=?").Where("gid=?").Prepare(), updateGroupPerms: set("permissions=?"),
} }
return acc.FirstError() return acc.FirstError()
}) })

View File

@ -141,22 +141,20 @@ func (s *MemoryGroupStore) GetCopy(id int) (Group, error) {
func (s *MemoryGroupStore) Reload(id int) error { func (s *MemoryGroupStore) Reload(id int) error {
// TODO: Reload this data too // TODO: Reload this data too
g, err := s.Get(id) g, e := s.Get(id)
if err != nil { if e != nil {
LogError(errors.New("can't get cansee data for group #" + strconv.Itoa(id))) LogError(errors.New("can't get cansee data for group #" + strconv.Itoa(id)))
return nil return nil
} }
canSee := g.CanSee canSee := g.CanSee
g = &Group{ID: id, CanSee: 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) e = s.get.QueryRow(id).Scan(&g.Name, &g.PermissionsText, &g.PluginPermsText, &g.IsMod, &g.IsAdmin, &g.IsBanned, &g.Tag)
if err != nil { if e != nil {
return err return e
} }
if e = s.initGroup(g); e != nil {
err = s.initGroup(g) LogError(e)
if err != nil {
LogError(err)
return nil return nil
} }
@ -166,19 +164,19 @@ func (s *MemoryGroupStore) Reload(id int) error {
} }
func (s *MemoryGroupStore) initGroup(g *Group) error { func (s *MemoryGroupStore) initGroup(g *Group) error {
err := json.Unmarshal(g.PermissionsText, &g.Perms) e := json.Unmarshal(g.PermissionsText, &g.Perms)
if err != nil { if e != nil {
log.Printf("group: %+v\n", g) log.Printf("g: %+v\n", g)
log.Print("bad group perms: ", g.PermissionsText) log.Print("bad group perms: ", g.PermissionsText)
return err return e
} }
DebugLogf(g.Name+": %+v\n", g.Perms) DebugLogf(g.Name+": %+v\n", g.Perms)
err = json.Unmarshal(g.PluginPermsText, &g.PluginPerms) e = json.Unmarshal(g.PluginPermsText, &g.PluginPerms)
if err != nil { if e != nil {
log.Printf("group: %+v\n", g) log.Printf("g: %+v\n", g)
log.Print("bad group plugin perms: ", g.PluginPermsText) log.Print("bad group plugin perms: ", g.PluginPermsText)
return err return e
} }
DebugLogf(g.Name+": %+v\n", g.PluginPerms) DebugLogf(g.Name+": %+v\n", g.PluginPerms)
@ -188,9 +186,9 @@ func (s *MemoryGroupStore) initGroup(g *Group) error {
g.IsBanned = false g.IsBanned = false
} }
err = s.userCount.QueryRow(g.ID).Scan(&g.UserCount) e = s.userCount.QueryRow(g.ID).Scan(&g.UserCount)
if err != sql.ErrNoRows { if e != sql.ErrNoRows {
return err return e
} }
return nil return nil
} }

View File

@ -655,6 +655,12 @@ type PanelEditForumPage struct {
Active bool Active bool
Preset string Preset string
Groups []GroupForumPermPreset Groups []GroupForumPermPreset
Actions []*ForumActionAction
}
type ForumActionAction struct {
*ForumAction
ActionName string
} }
type NameLangToggle struct { type NameLangToggle struct {
@ -793,11 +799,12 @@ type PanelDebugPage struct {
DBVersion string DBVersion string
Uptime string Uptime string
OpenConns int DBConns int
DBAdapter string DBAdapter string
Goroutines int Goroutines int
CPUs int CPUs int
HttpConns int
Tasks DebugPageTasks Tasks DebugPageTasks
MemStats runtime.MemStats MemStats runtime.MemStats

View File

@ -39,11 +39,11 @@ func NewDefaultBlockStore(acc *qgen.Accumulator) (*DefaultBlockStore, error) {
} }
func (s *DefaultBlockStore) IsBlockedBy(blocker, blockee int) (bool, error) { func (s *DefaultBlockStore) IsBlockedBy(blocker, blockee int) (bool, error) {
err := s.isBlocked.QueryRow(blocker, blockee).Scan(&blocker) e := s.isBlocked.QueryRow(blocker, blockee).Scan(&blocker)
if err == ErrNoRows { if e == ErrNoRows {
return false, nil 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? // 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) return s.IsBlockedBy(blockers[0], blockee)
} }
idList, q := inqbuild(blockers) idList, q := inqbuild(blockers)
count, err := qgen.NewAcc().Count("users_blocks").Where("blocker IN(" + q + ") AND blockedUser=?").TotalP(idList...) count, e := qgen.NewAcc().Count("users_blocks").Where("blocker IN(" + q + ") AND blockedUser=?").TotalP(idList...)
if err == ErrNoRows { if e == ErrNoRows {
return false, nil return false, nil
} }
return count == 0, err return count == 0, e
} }
func (s *DefaultBlockStore) Add(blocker, blockee int) error { func (s *DefaultBlockStore) Add(blocker, blockee int) error {
_, err := s.add.Exec(blocker, blockee) _, e := s.add.Exec(blocker, blockee)
return err return e
} }
func (s *DefaultBlockStore) Remove(blocker, blockee int) error { func (s *DefaultBlockStore) Remove(blocker, blockee int) error {
_, err := s.remove.Exec(blocker, blockee) _, e := s.remove.Exec(blocker, blockee)
return err return e
} }
func (s *DefaultBlockStore) BlockedByOffset(blocker, offset, perPage int) (uids []int, err error) { func (s *DefaultBlockStore) BlockedByOffset(blocker, offset, perPage int) (uids []int, err error) {
rows, err := s.blockedBy.Query(blocker, offset, perPage) rows, e := s.blockedBy.Query(blocker, offset, perPage)
if err != nil { if e != nil {
return nil, err return nil, e
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var uid int var uid int
err := rows.Scan(&uid) if e := rows.Scan(&uid); e != nil {
if err != nil { return nil, e
return nil, err
} }
uids = append(uids, uid) 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) { func (s *DefaultBlockStore) BlockedByCount(blocker int) (count int) {
err := s.blockedByCount.QueryRow(blocker).Scan(&count) e := s.blockedByCount.QueryRow(blocker).Scan(&count)
if err != nil { if e != nil {
LogError(err) LogError(e)
} }
return count return count
} }
@ -127,13 +126,13 @@ func NewDefaultFriendStore(acc *qgen.Accumulator) (*DefaultFriendStore, error) {
} }
func (s *DefaultFriendStore) AddInvite(requester, target int) error { func (s *DefaultFriendStore) AddInvite(requester, target int) error {
_, err := s.addInvite.Exec(requester, target) _, e := s.addInvite.Exec(requester, target)
return err return e
} }
func (s *DefaultFriendStore) Confirm(requester, target int) error { func (s *DefaultFriendStore) Confirm(requester, target int) error {
_, err := s.confirm.Exec(requester, target) _, e := s.confirm.Exec(requester, target)
return err return e
} }
func (s *DefaultFriendStore) GetOwnSentInvites(uid int) ([]FriendInvite, error) { func (s *DefaultFriendStore) GetOwnSentInvites(uid int) ([]FriendInvite, error) {

View File

@ -11,7 +11,7 @@ import (
"log" "log"
"time" "time"
"github.com/Azareal/Gosora/query_gen" qgen "github.com/Azareal/Gosora/query_gen"
) )
type TaskStmts struct { type TaskStmts struct {
@ -68,18 +68,22 @@ func AddShutdownTask(task func() error) {
func ScheduledHalfSecondTaskCount() int { func ScheduledHalfSecondTaskCount() int {
return len(ScheduledHalfSecondTasks) return len(ScheduledHalfSecondTasks)
} }
// ScheduledSecondTaskCount is not concurrency safe // ScheduledSecondTaskCount is not concurrency safe
func ScheduledSecondTaskCount() int { func ScheduledSecondTaskCount() int {
return len(ScheduledSecondTasks) return len(ScheduledSecondTasks)
} }
// ScheduledFifteenMinuteTaskCount is not concurrency safe // ScheduledFifteenMinuteTaskCount is not concurrency safe
func ScheduledFifteenMinuteTaskCount() int { func ScheduledFifteenMinuteTaskCount() int {
return len(ScheduledFifteenMinuteTasks) return len(ScheduledFifteenMinuteTasks)
} }
// ScheduledHourTaskCount is not concurrency safe // ScheduledHourTaskCount is not concurrency safe
func ScheduledHourTaskCount() int { func ScheduledHourTaskCount() int {
return len(ScheduledHourTasks) return len(ScheduledHourTasks)
} }
// ShutdownTaskCount is not concurrency safe // ShutdownTaskCount is not concurrency safe
func ShutdownTaskCount() int { func ShutdownTaskCount() int {
return len(ShutdownTasks) return len(ShutdownTasks)
@ -87,25 +91,23 @@ func ShutdownTaskCount() int {
// TODO: Use AddScheduledSecondTask // TODO: Use AddScheduledSecondTask
func HandleExpiredScheduledGroups() error { func HandleExpiredScheduledGroups() error {
rows, err := taskStmts.getExpiredScheduledGroups.Query() rows, e := taskStmts.getExpiredScheduledGroups.Query()
if err != nil { if e != nil {
return err return e
} }
defer rows.Close() defer rows.Close()
var uid int var uid int
for rows.Next() { for rows.Next() {
err := rows.Scan(&uid) if e := rows.Scan(&uid); e != nil {
if err != nil { return e
return err
} }
// Sneaky way of initialising a *User, please use the methods on the UserStore instead // Sneaky way of initialising a *User, please use the methods on the UserStore instead
user := BlankUser() user := BlankUser()
user.ID = uid user.ID = uid
err = user.RevertGroupUpdate() e = user.RevertGroupUpdate()
if err != nil { if e != nil {
return err return e
} }
} }
return rows.Err() return rows.Err()
@ -122,28 +124,28 @@ func HandleServerSync() error {
} }
var lastUpdate time.Time var lastUpdate time.Time
err := taskStmts.getSync.QueryRow().Scan(&lastUpdate) e := taskStmts.getSync.QueryRow().Scan(&lastUpdate)
if err != nil { if e != nil {
return err return e
} }
if lastUpdate.After(lastSync) { if lastUpdate.After(lastSync) {
err = Forums.LoadForums() e = Forums.LoadForums()
if err != nil { if e != nil {
log.Print("Unable to reload the forums") log.Print("Unable to reload the forums")
return err return e
} }
// TODO: Resync the groups // TODO: Resync the groups
// TODO: Resync the permissions // TODO: Resync the permissions
err = LoadSettings() e = LoadSettings()
if err != nil { if e != nil {
log.Print("Unable to reload the settings") log.Print("Unable to reload the settings")
return err return e
} }
err = WordFilters.ReloadAll() e = WordFilters.ReloadAll()
if err != nil { if e != nil {
log.Print("Unable to reload the word filters") log.Print("Unable to reload the word filters")
return err return e
} }
} }
return nil return nil

View File

@ -385,7 +385,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true} 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} 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} 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_debug", "c.PanelDebugPage", dpage)
//t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter}) //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 == "" { if content == "" {
return //log.Fatal("No content body for " + name) return //log.Fatal("No content body for " + name)
} }
err := writeFile("./tmpl_"+name+".go", content) e := writeFile("./tmpl_"+name+".go", content)
if err != nil { if e != nil {
log.Fatal(err) log.Fatal(e)
} }
} }
wg.Add(1) wg.Add(1)
@ -595,9 +595,9 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
if tname != "" { if tname != "" {
tname = "_" + tname tname = "_" + tname
} }
err := writeFile(dirPrefix+"tmpl_"+name+tname+".jgo", content) e := writeFile(dirPrefix+"tmpl_"+name+tname+".jgo", content)
if err != nil { if e != nil {
log.Fatal(err) log.Fatal(e)
} }
wg.Done() wg.Done()
}() }()
@ -737,9 +737,9 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string)
log.Print("Writing template list") log.Print("Writing template list")
wg.Add(1) wg.Add(1)
go func() { go func() {
err := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix)) e := writeFile(prefix+"tmpl_list.go", getTemplateList(c, wg, prefix))
if err != nil { if e != nil {
log.Fatal(err) log.Fatal(e)
} }
wg.Done() wg.Done()
}() }()

View File

@ -199,6 +199,7 @@ type TopicStmts struct {
deleteActivity *sql.Stmt deleteActivity *sql.Stmt
edit *sql.Stmt edit *sql.Stmt
setPoll *sql.Stmt setPoll *sql.Stmt
testSetCreatedAt *sql.Stmt
createAction *sql.Stmt createAction *sql.Stmt
getTopicUser *sql.Stmt // TODO: Can we get rid of this? getTopicUser *sql.Stmt // TODO: Can we get rid of this?
@ -209,31 +210,35 @@ var topicStmts TopicStmts
func init() { func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { 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{ 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", "?,?"), 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", "?,?"), 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(), getReplies3: acc.Select("replies").Columns("rid,content,createdBy,createdAt,lastEdit,lastEditBy,likeCount,attachCount,actionType").Where(w).Orderby("rid ASC").Limit("?,?").Prepare(),
addReplies: acc.Update(t).Set("postCount=postCount+?, lastReplyBy=?, lastReplyAt=UTC_TIMESTAMP()").Where("tid=?").Prepare(), addReplies: set("postCount=postCount+?,lastReplyBy=?,lastReplyAt=UTC_TIMESTAMP()"),
updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID<? AND tid=?").Prepare(), updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID<? AND tid=?").Prepare(),
lock: acc.Update(t).Set("is_closed=1").Where("tid=?").Prepare(), lock: set("is_closed=1"),
unlock: acc.Update(t).Set("is_closed=0").Where("tid=?").Prepare(), unlock: set("is_closed=0"),
moveTo: acc.Update(t).Set("parentID=?").Where("tid=?").Prepare(), moveTo: set("parentID=?"),
stick: acc.Update(t).Set("sticky=1").Where("tid=?").Prepare(), stick: set("sticky=1"),
unstick: acc.Update(t).Set("sticky=0").Where("tid=?").Prepare(), unstick: set("sticky=0"),
hasLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy=? and targetItem=? and targetType='topics'").Prepare(), hasLikedTopic: acc.Select("likes").Columns("targetItem").Where("sentBy=? and targetItem=? and targetType='topics'").Prepare(),
createLike: acc.Insert("likes").Columns("weight,targetItem,targetType,sentBy,createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(), createLike: acc.Insert("likes").Columns("weight,targetItem,targetType,sentBy,createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(),
addLikesToTopic: acc.Update(t).Set("likeCount=likeCount+?").Where("tid=?").Prepare(), addLikesToTopic: set("likeCount=likeCount+?"),
delete: acc.Delete(t).Where("tid=?").Prepare(), delete: acc.Delete(t).Where(w).Prepare(),
deleteReplies: acc.Delete("replies").Where("tid=?").Prepare(), deleteReplies: acc.Delete("replies").Where(w).Prepare(),
deleteLikesForTopic: acc.Delete("likes").Where("targetItem=? AND targetType='topics'").Prepare(), deleteLikesForTopic: acc.Delete("likes").Where("targetItem=? AND targetType='topics'").Prepare(),
deleteActivity: acc.Delete("activity_stream").Where("elementID=? AND elementType='topic'").Prepare(), deleteActivity: acc.Delete("activity_stream").Where("elementID=? AND elementType='topic'").Prepare(),
edit: acc.Update(t).Set("title=?,content=?,parsed_content=?").Where("tid=?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter? edit: set("title=?,content=?,parsed_content=?"), // TODO: Only run the content update bits on non-polls, does this matter?
setPoll: acc.Update(t).Set("poll=?").Where("tid=? AND poll=0").Prepare(), setPoll: acc.Update(t).Set("poll=?").Where("tid=? AND poll=0").Prepare(),
testSetCreatedAt: set("createdAt=?"),
createAction: acc.Insert("replies").Columns("tid,actionType,ip,createdBy,createdAt,lastUpdated,content,parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(), createAction: acc.Insert("replies").Columns("tid,actionType,ip,createdBy,createdAt,lastUpdated,content,parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(),
getTopicUser: acc.SimpleLeftJoin("topics AS t", "users AS u", "t.title, t.content, t.createdBy, t.createdAt, t.lastReplyAt, t.lastReplyBy, t.lastReplyID, t.is_closed, t.sticky, t.parentID, t.ip, t.views, t.postCount, t.likeCount, t.attachCount,t.poll, u.name, u.avatar, u.group, u.level", "t.createdBy=u.uid", "tid=?", "", ""), getTopicUser: acc.SimpleLeftJoin("topics AS t", "users AS u", "t.title, t.content, t.createdBy, t.createdAt, t.lastReplyAt, t.lastReplyBy, t.lastReplyID, t.is_closed, t.sticky, t.parentID, t.ip, t.views, t.postCount, t.likeCount, t.attachCount,t.poll, u.name, u.avatar, u.group, u.level", "t.createdBy=u.uid", w, "", ""),
getByReplyID: acc.SimpleLeftJoin("replies AS r", "topics AS t", "t.tid, t.title, t.content, t.createdBy, t.createdAt, t.is_closed, t.sticky, t.parentID, t.ip, t.views, t.postCount, t.likeCount, t.poll, t.data", "r.tid=t.tid", "rid=?", "", ""), getByReplyID: acc.SimpleLeftJoin("replies AS r", "topics AS t", "t.tid, t.title, t.content, t.createdBy, t.createdAt, t.is_closed, t.sticky, t.parentID, t.ip, t.views, t.postCount, t.likeCount, t.poll, t.data", "r.tid=t.tid", "rid=?", "", ""),
} }
return acc.FirstError() return acc.FirstError()
@ -250,52 +255,58 @@ func (t *Topic) cacheRemove() {
} }
// TODO: Write a test for this // TODO: Write a test for this
func (t *Topic) AddReply(rid, uid int) (err error) { func (t *Topic) AddReply(rid, uid int) (e error) {
_, err = topicStmts.addReplies.Exec(1, uid, t.ID) _, e = topicStmts.addReplies.Exec(1, uid, t.ID)
if err != nil { if e != nil {
return err return e
} }
_, err = topicStmts.updateLastReply.Exec(rid, rid, t.ID) _, e = topicStmts.updateLastReply.Exec(rid, rid, t.ID)
t.cacheRemove() t.cacheRemove()
return err return e
} }
func (t *Topic) Lock() (err error) { func (t *Topic) Lock() (e error) {
_, err = topicStmts.lock.Exec(t.ID) _, e = topicStmts.lock.Exec(t.ID)
t.cacheRemove() t.cacheRemove()
return err return e
} }
func (t *Topic) Unlock() (err error) { func (t *Topic) Unlock() (e error) {
_, err = topicStmts.unlock.Exec(t.ID) _, e = topicStmts.unlock.Exec(t.ID)
t.cacheRemove() t.cacheRemove()
return err return e
} }
func (t *Topic) MoveTo(destForum int) (err error) { func (t *Topic) MoveTo(destForum int) (e error) {
_, err = topicStmts.moveTo.Exec(destForum, t.ID) _, e = topicStmts.moveTo.Exec(destForum, t.ID)
t.cacheRemove() t.cacheRemove()
if err != nil { if e != nil {
return err return e
} }
err = Attachments.MoveTo(destForum, t.ID, "topics") e = Attachments.MoveTo(destForum, t.ID, "topics")
if err != nil { if e != nil {
return err return e
} }
return Attachments.MoveToByExtra(destForum, "replies", strconv.Itoa(t.ID)) return Attachments.MoveToByExtra(destForum, "replies", strconv.Itoa(t.ID))
} }
// TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same. func (t *Topic) TestSetCreatedAt(s time.Time) (e error) {
func (t *Topic) Stick() (err error) { _, e = topicStmts.testSetCreatedAt.Exec(s, t.ID)
_, err = topicStmts.stick.Exec(t.ID)
t.cacheRemove() t.cacheRemove()
return err return e
} }
func (t *Topic) Unstick() (err error) { // TODO: We might want more consistent terminology rather than using stick in some places and pin in others. If you don't understand the difference, there is none, they are one and the same.
_, err = topicStmts.unstick.Exec(t.ID) func (t *Topic) Stick() (e error) {
_, e = topicStmts.stick.Exec(t.ID)
t.cacheRemove() t.cacheRemove()
return err return e
}
func (t *Topic) Unstick() (e error) {
_, e = topicStmts.unstick.Exec(t.ID)
t.cacheRemove()
return e
} }
// TODO: Test this // TODO: Test this
@ -323,49 +334,46 @@ func (t *Topic) Like(score, uid int) (err error) {
// TODO: Use a transaction // TODO: Use a transaction
func (t *Topic) Unlike(uid int) error { func (t *Topic) Unlike(uid int) error {
err := Likes.Delete(t.ID, "topics") e := Likes.Delete(t.ID, "topics")
if err != nil { if e != nil {
return err return e
} }
_, err = topicStmts.addLikesToTopic.Exec(-1, t.ID) _, e = topicStmts.addLikesToTopic.Exec(-1, t.ID)
if err != nil { if e != nil {
return err return e
} }
_, err = userStmts.decLiked.Exec(1, uid) _, e = userStmts.decLiked.Exec(1, uid)
t.cacheRemove() t.cacheRemove()
return err return e
} }
func handleLikedTopicReplies(tid int) error { func handleLikedTopicReplies(tid int) error {
rows, err := userStmts.getLikedRepliesOfTopic.Query(tid) rows, e := userStmts.getLikedRepliesOfTopic.Query(tid)
if err != nil { if e != nil {
return err return e
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var rid int var rid int
err := rows.Scan(&rid) if e := rows.Scan(&rid); e != nil {
if err != nil { return e
return err
} }
_, err = replyStmts.deleteLikesForReply.Exec(rid) _, e = replyStmts.deleteLikesForReply.Exec(rid)
if err != nil { if e != nil {
return err return e
} }
err = Activity.DeleteByParams("like", rid, "post") e = Activity.DeleteByParams("like", rid, "post")
if err != nil { if e != nil {
return err return e
} }
} }
return rows.Err() return rows.Err()
} }
func handleTopicAttachments(tid int) error { func handleTopicAttachments(tid int) error {
err := handleAttachments(userStmts.getAttachmentsOfTopic, tid) e := handleAttachments(userStmts.getAttachmentsOfTopic, tid)
if err != nil { if e != nil {
return err return e
} }
return handleAttachments(userStmts.getAttachmentsOfTopic2, tid) return handleAttachments(userStmts.getAttachmentsOfTopic2, tid)
} }
@ -375,120 +383,113 @@ func handleReplyAttachments(rid int) error {
} }
func handleAttachments(stmt *sql.Stmt, id int) error { func handleAttachments(stmt *sql.Stmt, id int) error {
rows, err := stmt.Query(id) rows, e := stmt.Query(id)
if err != nil { if e != nil {
return err return e
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var aid int var aid int
err := rows.Scan(&aid) if e := rows.Scan(&aid); e != nil {
if err != nil { return e
return err
} }
a, err := Attachments.FGet(aid) a, e := Attachments.FGet(aid)
if err != nil { if e != nil {
return err return e
} }
err = deleteAttachment(a) e = deleteAttachment(a)
if err != nil && err != sql.ErrNoRows { if e != nil && e != sql.ErrNoRows {
return err return e
} }
} }
return rows.Err() return rows.Err()
} }
// TODO: Only load a row per createdBy, maybe with group by? // TODO: Only load a row per createdBy, maybe with group by?
func handleTopicReplies(umap map[int]struct{}, uid, tid int) error { func handleTopicReplies(umap map[int]struct{}, uid, tid int) error {
rows, err := userStmts.getRepliesOfTopic.Query(uid, tid) rows, e := userStmts.getRepliesOfTopic.Query(uid, tid)
if err != nil { if e != nil {
return err return e
} }
defer rows.Close() defer rows.Close()
var createdBy int var createdBy int
for rows.Next() { for rows.Next() {
err := rows.Scan(&createdBy) if e := rows.Scan(&createdBy); e != nil {
if err != nil { return e
return err
} }
umap[createdBy] = struct{}{} umap[createdBy] = struct{}{}
} }
return rows.Err() return rows.Err()
} }
// TODO: Use a transaction here // TODO: Use a transaction here
func (t *Topic) Delete() error { func (t *Topic) Delete() error {
/*creator, err := Users.Get(t.CreatedBy) /*creator, e := Users.Get(t.CreatedBy)
if err == nil { if e == nil {
err = creator.DecreasePostStats(WordCount(t.Content), true) e = creator.DecreasePostStats(WordCount(t.Content), true)
if err != nil { if e != nil {
return err return e
} }
} else if err != ErrNoRows { } else if e != ErrNoRows {
return err return e
}*/ }*/
// TODO: Clear reply cache too // TODO: Clear reply cache too
_, err := topicStmts.delete.Exec(t.ID) _, e := topicStmts.delete.Exec(t.ID)
t.cacheRemove() t.cacheRemove()
if err != nil { if e != nil {
return err return e
} }
err = Forums.RemoveTopic(t.ParentID) e = Forums.RemoveTopic(t.ParentID)
if err != nil && err != ErrNoRows { if e != nil && e != ErrNoRows {
return err return e
} }
_, err = topicStmts.deleteLikesForTopic.Exec(t.ID) _, e = topicStmts.deleteLikesForTopic.Exec(t.ID)
if err != nil { if e != nil {
return err return e
} }
if t.PostCount > 1 { if t.PostCount > 1 {
err = handleLikedTopicReplies(t.ID) if e = handleLikedTopicReplies(t.ID); e != nil {
if err != nil { return e
return err
} }
umap := make(map[int]struct{}) umap := make(map[int]struct{})
err = handleTopicReplies(umap, t.CreatedBy, t.ID) e = handleTopicReplies(umap, t.CreatedBy, t.ID)
if err != nil { if e != nil {
return err return e
} }
_, err = topicStmts.deleteReplies.Exec(t.ID) _, e = topicStmts.deleteReplies.Exec(t.ID)
if err != nil { if e != nil {
return err return e
} }
for uid := range umap { for uid := range umap {
err = (&User{ID: uid}).RecalcPostStats() e = (&User{ID: uid}).RecalcPostStats()
if err != nil { if e != nil {
//log.Printf("err: %+v\n", err) //log.Printf("e: %+v\n", e)
return err return e
} }
} }
} }
err = (&User{ID: t.CreatedBy}).RecalcPostStats() e = (&User{ID: t.CreatedBy}).RecalcPostStats()
if err != nil { if e != nil {
return err return e
} }
err = handleTopicAttachments(t.ID) e = handleTopicAttachments(t.ID)
if err != nil { if e != nil {
return err return e
} }
err = Subscriptions.DeleteResource(t.ID, "topic") e = Subscriptions.DeleteResource(t.ID, "topic")
if err != nil { if e != nil {
return err return e
} }
_, err = topicStmts.deleteActivity.Exec(t.ID) _, e = topicStmts.deleteActivity.Exec(t.ID)
if err != nil { if e != nil {
return err return e
} }
if t.Poll > 0 { if t.Poll > 0 {
err = (&Poll{ID: t.Poll}).Delete() e = (&Poll{ID: t.Poll}).Delete()
if err != nil { if e != nil {
return err return e
} }
} }
return nil return nil
@ -513,9 +514,9 @@ func (t *Topic) Update(name, content string) error {
} }
func (t *Topic) SetPoll(pollID int) 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() t.cacheRemove()
return err return e
} }
// TODO: Have this go through the ReplyStore? // TODO: Have this go through the ReplyStore?
@ -543,22 +544,19 @@ func (t *Topic) CreateActionReply(action, ip string, uid int) (err error) {
return err return err
} }
func GetRidsForTopic(tid, offset int) (rids []int, err error) { func GetRidsForTopic(tid, offset int) (rids []int, e error) {
rows, err := topicStmts.getRids.Query(tid, offset, Config.ItemsPerPage) rows, e := topicStmts.getRids.Query(tid, offset, Config.ItemsPerPage)
if err != nil { if e != nil {
return nil, err return nil, e
} }
defer rows.Close() defer rows.Close()
var rid int var rid int
for rows.Next() { for rows.Next() {
err := rows.Scan(&rid) if e := rows.Scan(&rid); e != nil {
if err != nil { return nil, e
return nil, err
} }
rids = append(rids, rid) rids = append(rids, rid)
} }
return rids, rows.Err() return rids, rows.Err()
} }

View File

@ -27,9 +27,9 @@ type weakpassHolder struct {
func InitWeakPasswords() error { func InitWeakPasswords() error {
var weakpass weakpassHolder var weakpass weakpassHolder
err := unmarshalJsonFile("./config/weakpass_default.json", &weakpass) e := unmarshalJsonFile("./config/weakpass_default.json", &weakpass)
if err != nil { if e != nil {
return err return e
} }
wcon := make(map[string]struct{}) wcon := make(map[string]struct{})
@ -41,9 +41,9 @@ func InitWeakPasswords() error {
} }
weakpass = weakpassHolder{} weakpass = weakpassHolder{}
err = unmarshalJsonFileIgnore404("./config/weakpass.json", &weakpass) e = unmarshalJsonFileIgnore404("./config/weakpass.json", &weakpass)
if err != nil { if e != nil {
return err return e
} }
for _, item := range weakpass.Contains { for _, item := range weakpass.Contains {

View File

@ -3,7 +3,7 @@
/* /*
* *
* Gosora WebSocket Subsystem * Gosora WebSocket Subsystem
* Copyright Azareal 2017 - 2020 * Copyright Azareal 2017 - 2021
* *
*/ */
package common package common
@ -149,12 +149,12 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
if wsUser.User.ID == 0 { if wsUser.User.ID == 0 {
return return
} }
_, tid, err := ParseSEOURL(page) _, tid, e := ParseSEOURL(page)
if err != nil { if e != nil {
return return
} }
topic, err := Topics.Get(tid) topic, e := Topics.Get(tid)
if err != nil { if e != nil {
return return
} }
if !Forums.Exists(topic.ParentID) { if !Forums.Exists(topic.ParentID) {
@ -169,10 +169,10 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
return return
}*/ }*/
fperms, err := FPStore.Get(topic.ParentID, usercpy.Group) fperms, e := FPStore.Get(topic.ParentID, usercpy.Group)
if err == ErrNoRows { if e == ErrNoRows {
fperms = BlankForumPerms() fperms = BlankForumPerms()
} else if err != nil { } else if e != nil {
return return
} }
cascadeForumPerms(fperms, usercpy) cascadeForumPerms(fperms, usercpy)
@ -202,9 +202,9 @@ func wsPageResponses(wsUser *WSUser, conn *websocket.Conn, page string) {
default: default:
return return
} }
err := wsUser.SetPageForSocket(conn, page) e := wsUser.SetPageForSocket(conn, page)
if err != nil { if e != nil {
LogError(err) LogError(e)
} }
} }
@ -248,8 +248,8 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
return return
} }
wsUser.FinalizePage(page, func() { wsUser.FinalizePage(page, func() {
_, tid, err := ParseSEOURL(page) _, tid, e := ParseSEOURL(page)
if err != nil { if e != nil {
return return
} }
topicMutex.Lock() topicMutex.Lock()
@ -258,8 +258,7 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
if !ok { if !ok {
return return
} }
_, ok = topic[wsUser] if _, ok = topic[wsUser]; !ok {
if !ok {
return return
} }
delete(topic, wsUser) delete(topic, wsUser)
@ -272,9 +271,9 @@ func wsLeavePage(wsUser *WSUser, conn *websocket.Conn, page string) {
delete(adminStatsWatchers, conn) delete(adminStatsWatchers, conn)
adminStatsMutex.Unlock() adminStatsMutex.Unlock()
} }
err := wsUser.SetPageForSocket(conn, "") e := wsUser.SetPageForSocket(conn, "")
if err != nil { if e != nil {
LogError(err) LogError(e)
} }
} }
@ -290,9 +289,7 @@ var adminStatsMutex sync.RWMutex
func adminStatsTicker() { func adminStatsTicker() {
time.Sleep(time.Second) time.Sleep(time.Second)
lastUonline := -1 lastUonline, lastGonline, lastTotonline := -1, -1, -1
lastGonline := -1
lastTotonline := -1
lastCPUPerc := -1 lastCPUPerc := -1
var lastAvailableRAM int64 = -1 var lastAvailableRAM int64 = -1
var noStatUpdates, noRAMUpdates bool var noStatUpdates, noRAMUpdates bool

View File

@ -73,10 +73,10 @@ func (w *Widget) Delete() error {
return nil return nil
} }
func (w *Widget) Copy() (owidget *Widget) { func (w *Widget) Copy() (ow *Widget) {
owidget = &Widget{} ow = &Widget{}
*owidget = *w *ow = *w
return owidget return ow
} }
// TODO: Test this // TODO: Test this

View File

@ -28,8 +28,8 @@ func (u *WSUser) Ping() error {
continue continue
} }
socket.conn.SetWriteDeadline(time.Now().Add(time.Minute)) socket.conn.SetWriteDeadline(time.Now().Add(time.Minute))
err := socket.conn.WriteMessage(websocket.PingMessage, nil) e := socket.conn.WriteMessage(websocket.PingMessage, nil)
if err != nil { if e != nil {
socket.conn.Close() socket.conn.Close()
} }
} }
@ -42,9 +42,9 @@ func (u *WSUser) WriteAll(msg string) error {
if socket == nil { if socket == nil {
continue continue
} }
w, err := socket.conn.NextWriter(websocket.TextMessage) w, e := socket.conn.NextWriter(websocket.TextMessage)
if err != nil { if e != nil {
return err return e
} }
_, _ = w.Write(msgbytes) _, _ = w.Write(msgbytes)
w.Close() w.Close()
@ -66,8 +66,8 @@ func (u *WSUser) WriteToPageBytes(msg []byte, page string) error {
if socket.Page != page { if socket.Page != page {
continue continue
} }
w, err := socket.conn.NextWriter(websocket.TextMessage) w, e := socket.conn.NextWriter(websocket.TextMessage)
if err != nil { if e != nil {
continue // Skip dead sockets, a dedicated goroutine handles those continue // Skip dead sockets, a dedicated goroutine handles those
} }
_, _ = w.Write(msg) _, _ = w.Write(msg)
@ -90,8 +90,8 @@ func (u *WSUser) WriteToPageBytesMulti(msgs [][]byte, page string) error {
if socket.Page != page { if socket.Page != page {
continue continue
} }
w, err := socket.conn.NextWriter(websocket.TextMessage) w, e := socket.conn.NextWriter(websocket.TextMessage)
if err != nil { if e != nil {
continue // Skip dead sockets, a dedicated goroutine handles those continue // Skip dead sockets, a dedicated goroutine handles those
} }
for _, msg := range msgs { for _, msg := range msgs {
@ -184,7 +184,7 @@ func (u *WSUser) InPage(page string) bool {
return false return false
} }
func (u *WSUser) FinalizePage(page string, handle func()) { func (u *WSUser) FinalizePage(page string, h func()) {
u.Lock() u.Lock()
defer u.Unlock() defer u.Unlock()
for _, socket := range u.Sockets { for _, socket := range u.Sockets {
@ -195,5 +195,5 @@ func (u *WSUser) FinalizePage(page string, handle func()) {
return return
} }
} }
handle() h()
} }

File diff suppressed because it is too large Load Diff

View File

@ -31,18 +31,18 @@ func Lookup(name string) (InstallAdapter, bool) {
func createAdmin() error { func createAdmin() error {
fmt.Println("Creating the admin user") fmt.Println("Creating the admin user")
hashedPassword, salt, err := BcryptGeneratePassword("password") hashedPassword, salt, e := BcryptGeneratePassword("password")
if err != nil { if e != nil {
return err return e
} }
// Build the admin user query // 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(),'',''") 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 err != nil { if e != nil {
return err return e
} }
// Run the admin user query // Run the admin user query
_, err = adminUserStmt.Exec(hashedPassword, salt) _, e = adminUserStmt.Exec(hashedPassword, salt)
return err return e
} }

View File

@ -56,7 +56,9 @@
"megapost_min_words":"Mega Post Minimum Words", "megapost_min_words":"Mega Post Minimum Words",
"meta_desc":"Meta Description", "meta_desc":"Meta Description",
"rapid_loading":"Rapid Loaded?", "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": { "PermPresets": {
@ -479,14 +481,14 @@
"panel_rank_guests":"Guests", "panel_rank_guests":"Guests",
"panel_rank_members":"Members", "panel_rank_members":"Members",
"panel_preset_everyone":"Everyone", "panel.preset_everyone":"Everyone",
"panel_preset_announcements":"Announcements", "panel.preset_announcements":"Announcements",
"panel_preset_member_only":"Member Only", "panel.preset_member_only":"Member Only",
"panel_preset_staff_only":"Staff Only", "panel.preset_staff_only":"Staff Only",
"panel_preset_admin_only":"Admin Only", "panel.preset_admin_only":"Admin Only",
"panel_preset_archive":"Archive", "panel.preset_archive":"Archive",
"panel_preset_custom":"Custom", "panel.preset_custom":"Custom",
"panel_preset_public":"Public", "panel.preset_public":"Public",
"panel_active_hidden":"Hidden", "panel_active_hidden":"Hidden",
"panel_perms_no_access":"No Access", "panel_perms_no_access":"No Access",
@ -693,7 +695,7 @@
"forum_start_one":"Start one?", "forum_start_one":"Start one?",
"forums_head":"Forums", "forums_head":"Forums",
"forums_no_description":"No description", "forums_no_desc":"No description",
"forums_none":"None", "forums_none":"None",
"forums_no_forums":"You don't have access to any forums.", "forums_no_forums":"You don't have access to any forums.",
@ -944,26 +946,40 @@
"panel.forums_create_head":"Add Forum", "panel.forums_create_head":"Add Forum",
"panel.forums_create_name_label":"Name", "panel.forums_create_name_label":"Name",
"panel.forums_create_name":"Super Secret Forum", "panel.forums_create_name":"Super Secret Forum",
"panel.forums_create_description_label":"Description", "panel.forums_create_desc_label":"Description",
"panel.forums_create_description":"Where all the super secret stuff happens", "panel.forums_create_desc":"Where all the super secret stuff happens",
"panel.forums_active_label":"Active", "panel.forums_active_label":"Active",
"panel.forums_preset_label":"Preset", "panel.forums_preset_label":"Preset",
"panel.forums_create_button":"Add Forum", "panel.forums_create_button":"Add Forum",
"panel.forums_update_order_button":"Update Order", "panel.forums_update_order_button":"Update Order",
"panel.forums_order_updated":"The forums have been successfully updated", "panel.forums_order_updated":"The forums have been successfully updated",
"panel_forum_head_suffix":" Forum", "panel.forum_head_suffix":" Forum",
"panel_forum_name":"Name", "panel.forum_name":"Name",
"panel_forum_name_placeholder":"General Forum", "panel.forum_name_placeholder":"General Forum",
"panel_forum_description":"Description", "panel.forum_desc":"Description",
"panel_forum_description_placeholder":"Where the general stuff happens", "panel.forum_desc_placeholder":"Where the general stuff happens",
"panel_forum_active":"Active", "panel.forum_active":"Active",
"panel_forum_preset":"Preset", "panel.forum_preset":"Preset",
"panel_forum_update_button":"Update Forum", "panel.forum_update_button":"Update Forum",
"panel_forum_permissions_head":"Forum Permissions", "panel.forum_permissions_head":"Forum Permissions",
"panel_forum_edit_button":"Edit", "panel.forum_edit_button":"Edit",
"panel_forum_short_update_button":"Update", "panel.forum_short_update_button":"Update",
"panel_forum_full_edit_button":"Full Edit", "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_forum_delete_are_you_sure":"Are you sure you want to delete the '%s' forum?",
"panel_groups_head":"Groups", "panel_groups_head":"Groups",
@ -1244,6 +1260,7 @@
"panel_debug_goroutine_count_label":"Goroutines", "panel_debug_goroutine_count_label":"Goroutines",
"panel_debug_cpu_count_label":"CPUs", "panel_debug_cpu_count_label":"CPUs",
"panel_debug_http_conns_label":"HTTP Conns",
"panel_debug_tasks":"Tasks", "panel_debug_tasks":"Tasks",
"panel_debug_tasks_half_second":"Half Second", "panel_debug_tasks_half_second":"Half Second",

12
main.go
View File

@ -143,10 +143,10 @@ func storeInit() (e error) {
if e != nil { if e != nil {
return ws(e) return ws(e)
} }
/*c.ForumActionStore, e = c.NewDefaultForumActionStore(acc) c.ForumActionStore, e = c.NewDefaultForumActionStore(acc)
if e != nil { if e != nil {
return ws(e) return ws(e)
}*/ }
c.Convos, e = c.NewDefaultConversationStore(acc) c.Convos, e = c.NewDefaultConversationStore(acc)
if e != nil { if e != nil {
return ws(e) return ws(e)
@ -549,8 +549,7 @@ func main() {
// Resource Management Goroutine // Resource Management Goroutine
go func() { go func() {
uc := c.Users.GetCache() uc, tc := c.Users.GetCache(), c.Topics.GetCache()
tc := c.Topics.GetCache()
if uc == nil && tc == nil { if uc == nil && tc == nil {
return return
} }
@ -630,7 +629,7 @@ func main() {
func startServer() { func startServer() {
// We might not need the timeouts, if we're behind a reverse-proxy like Nginx // 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 rtime := c.Config.ReadTimeout
if rtime == 0 { if rtime == 0 {
rtime = 8 rtime = 8
@ -651,7 +650,8 @@ func startServer() {
} }
return &http.Server{ return &http.Server{
Addr: addr, Addr: addr,
Handler: handler, Handler: h,
ConnState: c.ConnWatch.StateChange,
ReadTimeout: time.Duration(rtime) * time.Second, ReadTimeout: time.Duration(rtime) * time.Second,
WriteTimeout: time.Duration(wtime) * time.Second, WriteTimeout: time.Duration(wtime) * time.Second,

View File

@ -1128,8 +1128,8 @@ func TestReplyStore(t *testing.T) {
func testReplyStore(t *testing.T, newID int, ip string) { func testReplyStore(t *testing.T, newID int, ip string) {
ex, exf := exp(t), expf(t) ex, exf := exp(t), expf(t)
replyTest2 := func(r *c.Reply, err error, rid, parentID, createdBy int, content, ip string) { replyTest2 := func(r *c.Reply, e error, rid, parentID, createdBy int, content, ip string) {
expectNilErr(t, err) 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.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.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) 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) { replyTest := func(rid, parentID, createdBy int, content, ip string) {
r, err := c.Rstore.Get(rid) r, e := c.Rstore.Get(rid)
replyTest2(r, err, rid, parentID, createdBy, content, ip) replyTest2(r, e, rid, parentID, createdBy, content, ip)
r, err = c.Rstore.GetCache().Get(rid) r, e = c.Rstore.GetCache().Get(rid)
replyTest2(r, err, rid, parentID, createdBy, content, ip) 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: // ! 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) //_, 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)) 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) { func TestTopicList(t *testing.T) {
ex, exf := exp(t), expf(t) ex, exf := exp(t), expf(t)
fid, err := c.Forums.Create("Test Forum", "Desc for test forum", true, "") fid, err := c.Forums.Create("Test Forum", "Desc for test forum", true, "")

View File

@ -53,6 +53,7 @@ func init() {
addPatch(33, patch33) addPatch(33, patch33)
addPatch(34, patch34) addPatch(34, patch34)
addPatch(35, patch35) addPatch(35, patch35)
addPatch(36, patch36)
} }
func bcol(col string, val bool) qgen.DBTableColumn { 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 { func patch1(scanner *bufio.Scanner) error {
routes := map[string]string{ return renameRoutes(map[string]string{
"routeAccountEditCriticalSubmit": "routes.AccountEditCriticalSubmit", "routeAccountEditCriticalSubmit": "routes.AccountEditCriticalSubmit",
"routeAccountEditAvatar": "routes.AccountEditAvatar", "routeAccountEditAvatar": "routes.AccountEditAvatar",
"routeAccountEditAvatarSubmit": "routes.AccountEditAvatarSubmit", "routeAccountEditAvatarSubmit": "routes.AccountEditAvatarSubmit",
"routeAccountEditUsername": "routes.AccountEditUsername", "routeAccountEditUsername": "routes.AccountEditUsername",
"routeAccountEditUsernameSubmit": "routes.AccountEditUsernameSubmit", "routeAccountEditUsernameSubmit": "routes.AccountEditUsernameSubmit",
} })
return renameRoutes(routes)
} }
func patch2(scanner *bufio.Scanner) error { func patch2(scanner *bufio.Scanner) error {
routes := map[string]string{ return renameRoutes(map[string]string{
"routeLogout": "routes.AccountLogout", "routeLogout": "routes.AccountLogout",
"routeShowAttachment": "routes.ShowAttachment", "routeShowAttachment": "routes.ShowAttachment",
"routeChangeTheme": "routes.ChangeTheme", "routeChangeTheme": "routes.ChangeTheme",
@ -189,8 +189,7 @@ func patch2(scanner *bufio.Scanner) error {
"routeDynamic": "routes.DynamicRoute", "routeDynamic": "routes.DynamicRoute",
"routeUploads": "routes.UploadedFile", "routeUploads": "routes.UploadedFile",
"BadRoute": "routes.BadRoute", "BadRoute": "routes.BadRoute",
} })
return renameRoutes(routes)
} }
func patch3(scanner *bufio.Scanner) error { func patch3(scanner *bufio.Scanner) error {
@ -248,14 +247,14 @@ func patch4(scanner *bufio.Scanner) error {
"routePanelForumsEditPermsAdvanceSubmit": "panel.ForumsEditPermsAdvanceSubmit", "routePanelForumsEditPermsAdvanceSubmit": "panel.ForumsEditPermsAdvanceSubmit",
"routePanelBackups": "panel.Backups", "routePanelBackups": "panel.Backups",
} }
err := renameRoutes(routes) e := renameRoutes(routes)
if err != nil { if e != nil {
return err return e
} }
err = execStmt(qgen.Builder.SimpleDelete("settings", "name='url_tags'")) e = execStmt(qgen.Builder.SimpleDelete("settings", "name='url_tags'"))
if err != nil { if e != nil {
return err return e
} }
return createTable("pages", "utf8mb4", "utf8mb4_general_ci", return createTable("pages", "utf8mb4", "utf8mb4_general_ci",
@ -281,14 +280,14 @@ func patch5(scanner *bufio.Scanner) error {
"routes.AccountEditCritical": "routes.AccountEditPassword", "routes.AccountEditCritical": "routes.AccountEditPassword",
"routes.AccountEditCriticalSubmit": "routes.AccountEditPasswordSubmit", "routes.AccountEditCriticalSubmit": "routes.AccountEditPasswordSubmit",
} }
err := renameRoutes(routes) e := renameRoutes(routes)
if err != nil { if e != nil {
return err return e
} }
err = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path='/user/edit/'", "path='/user/edit/critical/'")) e = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path='/user/edit/'", "path='/user/edit/critical/'"))
if err != nil { if e != nil {
return err return e
} }
return createTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci", 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 { for key, value := range routes {
err := replaceTextWhere(key, value) e := replaceTextWhere(key, value)
if err != nil { if e != nil {
return err return e
} }
} }
@ -370,9 +369,9 @@ func patch8(scanner *bufio.Scanner) error {
"routePanelThemesMenuItemOrderSubmit": "panel.ThemesMenuItemOrderSubmit", "routePanelThemesMenuItemOrderSubmit": "panel.ThemesMenuItemOrderSubmit",
"routePanelDashboard": "panel.Dashboard", "routePanelDashboard": "panel.Dashboard",
} }
err := renameRoutes(routes) e := renameRoutes(routes)
if err != nil { if e != nil {
return err return e
} }
return createTable("updates", "", "", return createTable("updates", "", "",
@ -812,36 +811,36 @@ func patch29(scanner *bufio.Scanner) error {
} }
func patch30(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)) e := execStmt(qgen.Builder.AddColumn("users_groups_promotions", tC{"registeredFor", "int", 0, false, false, "0"}, nil))
if err != nil { if e != nil {
return err return e
} }
return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", "")) return execStmt(qgen.Builder.SetDefaultColumn("users", "last_ip", "varchar", ""))
} }
func patch31(scanner *bufio.Scanner) (e error) { func patch31(scanner *bufio.Scanner) (e error) {
addKey := func(tbl, col string, tk qgen.DBTableKey) error { addKey := func(tbl, col string, tk qgen.DBTableKey) error {
/*err := execStmt(qgen.Builder.RemoveIndex(tbl, col)) /*e := execStmt(qgen.Builder.RemoveIndex(tbl, col))
if err != nil { if e != nil {
return err return e
}*/ }*/
return execStmt(qgen.Builder.AddKey(tbl, col, tk)) return execStmt(qgen.Builder.AddKey(tbl, col, tk))
} }
err := addKey("topics", "title", tK{"title", "fulltext", "", false}) e = addKey("topics", "title", tK{"title", "fulltext", "", false})
if err != nil { if e != nil {
return err return e
} }
err = addKey("topics", "content", tK{"content", "fulltext", "", false}) e = addKey("topics", "content", tK{"content", "fulltext", "", false})
if err != nil { if e != nil {
return err return e
} }
return addKey("replies", "content", tK{"content", "fulltext", "", false}) return addKey("replies", "content", tK{"content", "fulltext", "", false})
} }
func createTable(tbl, charset, collation string, cols []tC, keys []tK) error { func createTable(tbl, charset, collation string, cols []tC, keys []tK) error {
err := execStmt(qgen.Builder.DropTable(tbl)) e := execStmt(qgen.Builder.DropTable(tbl))
if err != nil { if e != nil {
return err return e
} }
return execStmt(qgen.Builder.CreateTable(tbl, charset, collation, cols, keys)) 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 { func patch35(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("topics", tC{"weekEvenViews", "int", 0, false, false, "0"}, nil)) e := execStmt(qgen.Builder.AddColumn("topics", tC{"weekEvenViews", "int", 0, false, false, "0"}, nil))
if err != nil { if e != nil {
return err return e
} }
return execStmt(qgen.Builder.AddColumn("topics", tC{"weekOddViews", "int", 0, false, false, "0"}, nil)) 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'"))
}

View File

@ -32,6 +32,11 @@ func (b *accDeleteBuilder) DateOlderThan(col string, quantity int, unit string)
return b 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 { /*func (b *accDeleteBuilder) Prepare() *sql.Stmt {
return b.build.SimpleDelete(b.table, b.where) return b.build.SimpleDelete(b.table, b.where)
}*/ }*/
@ -87,6 +92,11 @@ func (b *accUpdateBuilder) DateOlderThan(col string, quantity int, unit string)
return b 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 { func (b *accUpdateBuilder) WhereQ(sel *selectPrebuilder) *accUpdateBuilder {
b.up.whereSubQuery = sel b.up.whereSubQuery = sel
return b return b
@ -98,14 +108,24 @@ func (b *accUpdateBuilder) Prepare() *sql.Stmt {
} }
return b.build.prepare(b.build.adapter.SimpleUpdate(b.up)) 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) { 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 { if err != nil {
return res, err return res, err
} }
//fmt.Println("query:", query) //fmt.Println("q:", q)
return b.build.exec(query, args...) return b.build.exec(q, args...)
}
type AccBuilder interface {
Prepare() *sql.Stmt
} }
type AccSelectBuilder struct { type AccSelectBuilder struct {
@ -147,8 +167,8 @@ func (b *AccSelectBuilder) In(col string, inList []int) *AccSelectBuilder {
// TODO: Optimise this // TODO: Optimise this
where := col + " IN(" where := col + " IN("
for _, item := range inList { for _, it := range inList {
where += strconv.Itoa(item) + "," where += strconv.Itoa(it) + ","
} }
where = where[:len(where)-1] + ")" where = where[:len(where)-1] + ")"
if b.where != "" { 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) 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 { func (b *AccSelectBuilder) ComplexPrepare() *sql.Stmt {
selectBuilder := b.build.GetAdapter().Builder().Select().FromAcc(b) selectBuilder := b.build.GetAdapter().Builder().Select().FromAcc(b)
return b.build.prepare(b.build.GetAdapter().ComplexSelect(selectBuilder)) return b.build.prepare(b.build.GetAdapter().ComplexSelect(selectBuilder))

View File

@ -199,6 +199,8 @@ func panelRoutes() *RouteGroup {
Action("panel.ForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"), Action("panel.ForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
View("panel.ForumsEditPermsAdvance", "/panel/forums/edit/perms/", "extraData"), View("panel.ForumsEditPermsAdvance", "/panel/forums/edit/perms/", "extraData"),
Action("panel.ForumsEditPermsAdvanceSubmit", "/panel/forums/edit/perms/adv/submit/", "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.Settings", "/panel/settings/"),
View("panel.SettingEdit", "/panel/settings/edit/", "extraData"), View("panel.SettingEdit", "/panel/settings/edit/", "extraData"),

View File

@ -18,10 +18,10 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
goVersion := runtime.Version() goVersion := runtime.Version()
dbVersion := qgen.Builder.DbVersion() dbVersion := qgen.Builder.DbVersion()
upDuration := time.Since(c.StartTime) upDur := time.Since(c.StartTime)
hours := int(upDuration.Hours()) hours := int(upDur.Hours())
mins := int(upDuration.Minutes()) mins := int(upDur.Minutes())
secs := int(upDuration.Seconds()) secs := int(upDur.Seconds())
var uptime string var uptime string
if hours > 24 { if hours > 24 {
days := 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? // TODO: Fetch the adapter from Builder rather than getting it from a global?
goroutines := runtime.NumGoroutine() goroutines := runtime.NumGoroutine()
cpus := runtime.NumCPU() cpus := runtime.NumCPU()
httpConns := c.ConnWatch.Count()
debugTasks := c.DebugPageTasks{c.ScheduledHalfSecondTaskCount(), c.ScheduledSecondTaskCount(), c.ScheduledFifteenMinuteTaskCount(), c.ScheduledHourTaskCount(), c.ShutdownTaskCount()} debugTasks := c.DebugPageTasks{c.ScheduledHalfSecondTaskCount(), c.ScheduledSecondTaskCount(), c.ScheduledFifteenMinuteTaskCount(), c.ScheduledHourTaskCount(), c.ShutdownTaskCount()}
var memStats runtime.MemStats 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 tlen, ulen, rlen int
var tcap, ucap, rcap int var tcap, ucap, rcap int
tcache := c.Topics.GetCache() tc := c.Topics.GetCache()
if tcache != nil { if tc != nil {
tlen = tcache.Length() tlen, tcap = tc.Length(), tc.GetCapacity()
tcap = tcache.GetCapacity()
} }
ucache := c.Users.GetCache() uc := c.Users.GetCache()
if ucache != nil { if uc != nil {
ulen = ucache.Length() ulen, ucap = uc.Length(), uc.GetCapacity()
ucap = ucache.GetCapacity()
} }
rcache := c.Rstore.GetCache() rc := c.Rstore.GetCache()
if rcache != nil { if rc != nil {
rlen = rcache.Length() rlen, rcap = rc.Length(), rc.GetCapacity()
rcap = rcache.GetCapacity()
} }
topicListThawed := c.TopicListThaw.Thawed() topicListThawed := c.TopicListThaw.Thawed()
@ -121,12 +119,13 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
if fErr != nil { if fErr != nil {
return c.InternalError(fErr, w, r) return c.InternalError(fErr, w, r)
} }
// TODO: How can we measure this without freezing up the entire page?
//gitSize, _ := c.DirSize("./.git") //gitSize, _ := c.DirSize("./.git")
gitSize := 0 gitSize := 0
debugDisk := c.DebugPageDisk{staticSize, attachSize, uploadsSize, logsSize, backupsSize, gitSize} 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}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug", pi})
} }

View File

@ -144,6 +144,7 @@ func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.Rout
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
// TODO: Move this even earlier?
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !u.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissionsJSQ(w, r, u, js) 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") basePage.Header.AddScriptAsync("panel_forum_edit.js")
forum, err := c.Forums.Get(fid) f, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u) return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if forum.Preset == "" { if f.Preset == "" {
forum.Preset = "custom" f.Preset = "custom"
} }
glist, err := c.Groups.GetAll() 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") 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}) 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) 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 { if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This forum doesn't exist", w, r, u, js) return c.LocalErrorJSQ("This forum doesn't exist", w, r, u, js)
} else if err != nil { } 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")) permPreset := c.StripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
err = forum.SetPreset(permPreset, gid) err = f.SetPreset(permPreset, gid)
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, u, js) 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 // 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, "-") params := strings.Split(paramList, "-")
if len(params) != 2 { if len(params) != 2 {
return fid, gid, errors.New("Parameter count mismatch") return fid, gid, errors.New("Parameter count mismatch")
} }
fid, e = strconv.Atoi(params[0])
fid, err = strconv.Atoi(params[0]) if e != nil {
if err != nil {
return fid, gid, errors.New("The provided Forum ID is not a valid number.") return fid, gid, errors.New("The provided Forum ID is not a valid number.")
} }
gid, e = strconv.Atoi(params[1])
gid, err = strconv.Atoi(params[1]) if e != nil {
if err != nil { e = errors.New("The provided Group ID is not a valid number.")
err = errors.New("The provided Group ID is not a valid number.")
} }
return fid, gid, e
return fid, gid, err
} }
func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, u *c.User, paramList string) c.RouteError { 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) return c.LocalError(err.Error(), w, r, u)
} }
forum, err := c.Forums.Get(fid) f, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u) return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u)
} else if err != nil { } 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) return c.InternalError(err, w, r)
} }
extractPerm := func(name string) bool { ep := func(name string) bool {
pvalue := r.PostFormValue("perm-" + name) pvalue := r.PostFormValue("perm-" + name)
return (pvalue == "1") return (pvalue == "1")
} }
// TODO: Generate this code? // TODO: Generate this code?
fp.ViewTopic = extractPerm("ViewTopic") fp.ViewTopic = ep("ViewTopic")
fp.LikeItem = extractPerm("LikeItem") fp.LikeItem = ep("LikeItem")
fp.CreateTopic = extractPerm("CreateTopic") fp.CreateTopic = ep("CreateTopic")
fp.EditTopic = extractPerm("EditTopic") fp.EditTopic = ep("EditTopic")
fp.DeleteTopic = extractPerm("DeleteTopic") fp.DeleteTopic = ep("DeleteTopic")
fp.CreateReply = extractPerm("CreateReply") fp.CreateReply = ep("CreateReply")
fp.EditReply = extractPerm("EditReply") fp.EditReply = ep("EditReply")
fp.DeleteReply = extractPerm("DeleteReply") fp.DeleteReply = ep("DeleteReply")
fp.PinTopic = extractPerm("PinTopic") fp.PinTopic = ep("PinTopic")
fp.CloseTopic = extractPerm("CloseTopic") fp.CloseTopic = ep("CloseTopic")
fp.MoveTopic = extractPerm("MoveTopic") fp.MoveTopic = ep("MoveTopic")
err = forum.SetPerms(&fp, "custom", gid) err = f.SetPerms(&fp, "custom", gid)
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, u, js) 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) 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)
}

View File

@ -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? // TODO: What if this list gets too long?
filters, err := c.WordFilters.GetAll() filters, e := c.WordFilters.GetAll()
if err != nil { if e != nil {
return c.InternalError(err, w, r) return c.InternalError(e, w, r)
} }
pi := c.PanelPage{basePage, tList, filters} 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 // 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")) replace := strings.TrimSpace(r.PostFormValue("replace"))
wfid, err := c.WordFilters.Create(find, replace) wfid, e := c.WordFilters.Create(find, replace)
if err != nil { if e != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(e, w, r, js)
} }
err = c.AdminLogs.Create("create", wfid, "word_filter", u.GetIP(), u.ID) e = c.AdminLogs.Create("create", wfid, "word_filter", u.GetIP(), u.ID)
if err != nil { if e != nil {
return c.InternalError(err, w, r) return c.InternalError(e, w, r)
} }
return successRedirect("/panel/settings/word-filters/", w, r, js) return successRedirect("/panel/settings/word-filters/", w, r, js)

View File

@ -13,8 +13,8 @@ import (
func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList { func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList {
wsTopicList := make([]*c.WsTopicsRow, len(topicList)) wsTopicList := make([]*c.WsTopicsRow, len(topicList))
for i, topicRow := range topicList { for i, tr := range topicList {
wsTopicList[i] = topicRow.WebSockets() wsTopicList[i] = tr.WebSockets()
} }
return &c.WsTopicList{wsTopicList, lastPage, 0} return &c.WsTopicList{wsTopicList, lastPage, 0}
} }

View File

@ -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 ('meta_desc','','html-attribute');
INSERT INTO [settings] ([name],[content],[type]) VALUES ('rapid_loading','1','bool'); 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]) 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 [themes] ([uname],[default]) VALUES ('cosora',1);
INSERT INTO [emails] ([email],[uid],[validated]) VALUES ('admin@localhost',1,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'); 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 (4,2,'{"ViewTopic":true}');
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (5,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 [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 [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,'::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,'');
INSERT INTO [menus] () VALUES (); 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],[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); 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);

View File

@ -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 ('meta_desc','','html-attribute');
INSERT INTO `settings`(`name`,`content`,`type`) VALUES ('rapid_loading','1','bool'); 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`) 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 `themes`(`uname`,`default`) VALUES ('cosora',1);
INSERT INTO `emails`(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,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'); 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 (4,2,'{"ViewTopic":true}');
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (5,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 `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 `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,'::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,'');
INSERT INTO `menus`() VALUES (); 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`,`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); 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);

View File

@ -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;

View File

@ -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 ('meta_desc','','html-attribute');
INSERT INTO "settings"("name","content","type") VALUES ('rapid_loading','1','bool'); 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") 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 "themes"("uname","default") VALUES ('cosora',1);
INSERT INTO "emails"("email","uid","validated") VALUES ('admin@localhost',1,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'); 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 (4,2,'{"ViewTopic":true}');
INSERT INTO "forums_permissions"("gid","fid","permissions") VALUES (5,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 "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 "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,'::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,'');
INSERT INTO "menus"() VALUES (); 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","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); 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);

View File

@ -10,10 +10,12 @@ CREATE TABLE "users" (
`lastActiveAt` timestamp not null, `lastActiveAt` timestamp not null,
`session` varchar (200) DEFAULT '' not null, `session` varchar (200) DEFAULT '' not null,
`last_ip` 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, `enable_embeds` int DEFAULT -1 not null,
`email` varchar (200) DEFAULT '' not null, `email` varchar (200) DEFAULT '' not null,
`avatar` varchar (100) 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_prefix` varchar (20) DEFAULT '' not null,
`url_name` varchar (100) DEFAULT '' not null, `url_name` varchar (100) DEFAULT '' not null,
`level` smallint DEFAULT 0 not null, `level` smallint DEFAULT 0 not null,

View File

@ -5,6 +5,6 @@ CREATE TABLE "widgets" (
`type` varchar (100) not null, `type` varchar (100) not null,
`active` boolean DEFAULT 0 not null, `active` boolean DEFAULT 0 not null,
`location` varchar (100) not null, `location` varchar (100) not null,
`data` text DEFAULT '' not null, `data` text not null,
primary key(`wid`) primary key(`wid`)
); );

View File

@ -12,7 +12,7 @@
{{if .Desc}} {{if .Desc}}
<span class="rowsmall"itemprop="description">{{.Desc}}</span> <span class="rowsmall"itemprop="description">{{.Desc}}</span>
{{else}} {{else}}
<span class="rowsmall forum_nodesc">{{lang "forums_no_description"}}</span> <span class="rowsmall forum_nodesc">{{lang "forums_no_desc"}}</span>
{{end}} {{end}}
</span> </span>
<span class="forum_right shift_right"> <span class="forum_right shift_right">

View File

@ -13,16 +13,16 @@
{{template "panel_debug_stat_head.html" "panel_debug_adapter_label"}} {{template "panel_debug_stat_head.html" "panel_debug_adapter_label"}}
{{/** TODO: Use this for active database connections when Go 1.11 lands **/}} {{/** TODO: Use this for active database connections when Go 1.11 lands **/}}
{{template "panel_debug_stat_head_q.html"}} {{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.html" .DBAdapter}}
{{template "panel_debug_stat_q.html"}} {{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_goroutine_count_label"}}
{{template "panel_debug_stat_head.html" "panel_debug_cpu_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" .Goroutines}}
{{template "panel_debug_stat.html" .CPUs}} {{template "panel_debug_stat.html" .CPUs}}
{{template "panel_debug_stat_q.html"}} {{template "panel_debug_stat.html" .HttpConns}}
</div> </div>
{{template "panel_debug_subhead.html" "panel_debug_tasks"}} {{template "panel_debug_subhead.html" "panel_debug_tasks"}}
<div id="panel_debug"class="colstack_grid"> <div id="panel_debug"class="colstack_grid">

View File

@ -1,45 +1,45 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel.forum_head_suffix"}}</h1></div>
</div> </div>
<div id="panel_forum"class="colstack_item the_form"> <div id="panel_forum"class="colstack_item the_form">
<form action="/panel/forums/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}"method="post"> <form action="/panel/forums/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}"method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_name"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forum_name"}}</a></div>
<div class="formitem"><input name="forum_name"type="text"value="{{.Name}}"placeholder="{{lang "panel_forum_name_placeholder"}}"></div> <div class="formitem"><input name="forum_name"type="text"value="{{.Name}}"placeholder="{{lang "panel.forum_name_placeholder"}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_description"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forum_desc"}}</a></div>
<div class="formitem"><input name="forum_desc"type="text"value="{{.Desc}}"placeholder="{{lang "panel_forum_description_placeholder"}}"></div> <div class="formitem"><input name="forum_desc"type="text"value="{{.Desc}}"placeholder="{{lang "panel.forum_desc_placeholder"}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_active"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forum_active"}}</a></div>
<div class="formitem"><select name="forum_active"> <div class="formitem"><select name="forum_active">
<option{{if .Active}} selected{{end}} value=1>{{lang "option_yes"}}</option> <option{{if .Active}} selected{{end}} value=1>{{lang "option_yes"}}</option>
<option{{if not .Active}} selected{{end}} value=0>{{lang "option_no"}}</option> <option{{if not .Active}} selected{{end}} value=0>{{lang "option_no"}}</option>
</select></div> </select></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_preset"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forum_preset"}}</a></div>
<div class="formitem"> <div class="formitem">
<select name="forum_preset"> <select name="forum_preset">
<option{{if eq .Preset "all"}} selected{{end}} value="all">{{lang "panel_preset_everyone"}}</option> <option{{if eq .Preset "all"}} selected{{end}} value="all">{{lang "panel.preset_everyone"}}</option>
<option{{if eq .Preset "announce"}} selected{{end}} value="announce">{{lang "panel_preset_announcements"}}</option> <option{{if eq .Preset "announce"}} selected{{end}} value="announce">{{lang "panel.preset_announcements"}}</option>
<option{{if eq .Preset "members"}} selected{{end}} value="members">{{lang "panel_preset_member_only"}}</option> <option{{if eq .Preset "members"}} selected{{end}} value="members">{{lang "panel.preset_member_only"}}</option>
<option{{if eq .Preset "staff"}} selected{{end}} value="staff">{{lang "panel_preset_staff_only"}}</option> <option{{if eq .Preset "staff"}} selected{{end}} value="staff">{{lang "panel.preset_staff_only"}}</option>
<option{{if eq .Preset "admins"}} selected{{end}} value="admins">{{lang "panel_preset_admin_only"}}</option> <option{{if eq .Preset "admins"}} selected{{end}} value="admins">{{lang "panel.preset_admin_only"}}</option>
<option{{if eq .Preset "archive"}} selected{{end}} value="archive">{{lang "panel_preset_archive"}}</option> <option{{if eq .Preset "archive"}} selected{{end}} value="archive">{{lang "panel.preset_archive"}}</option>
<option{{if eq .Preset "custom"}} selected{{end}} value="custom">{{lang "panel_preset_custom"}}</option> <option{{if eq .Preset "custom"}} selected{{end}} value="custom">{{lang "panel.preset_custom"}}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel_forum_update_button"}}</button></div> <div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel.forum_update_button"}}</button></div>
</div> </div>
</form> </form>
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "panel_forum_permissions_head"}}</h1> <h1>{{lang "panel.forum_permissions_head"}}</h1>
</div> </div>
</div> </div>
<div id="forum_quick_perms"class="colstack_item rowlist formlist the_form"> <div id="forum_quick_perms"class="colstack_item rowlist formlist the_form">
@ -48,13 +48,76 @@
<div class="formitem editable_parent"> <div class="formitem editable_parent">
<a>{{.Group.Name}}</a> <a>{{.Group.Name}}</a>
<input name="gid"value="{{.Group.ID}}"type="hidden"class="editable_block"data-field="gid"data-type="hidden"data-value="{{.Group.ID}}"> <input name="gid"value="{{.Group.ID}}"type="hidden"class="editable_block"data-field="gid"data-type="hidden"data-value="{{.Group.ID}}">
<span class="edit_fields hide_on_edit rowsmall">{{lang "panel_forum_edit_button"}}</span> <span class="edit_fields hide_on_edit rowsmall">{{lang "panel.forum_edit_button"}}</span>
<div class="panel_floater"> <div class="panel_floater">
<span data-field="perm_preset"data-type="list"data-value="{{.Preset}}"class="editable_block perm_preset perm_preset_{{.Preset}}"></span> <span data-field="perm_preset"data-type="list"data-value="{{.Preset}}"class="editable_block perm_preset perm_preset_{{.Preset}}"></span>
<a class="panel_right_button has_inner_button show_on_edit"href="/panel/forums/edit/perms/submit/{{$.ID}}"><button class='panel_tag submit_edit'type='submit'>{{lang "panel_forum_short_update_button"}}</button></a> <a class="panel_right_button has_inner_button show_on_edit"href="/panel/forums/edit/perms/submit/{{$.ID}}"><button class='panel_tag submit_edit'type='submit'>{{lang "panel.forum_short_update_button"}}</button></a>
<a class="panel_right_button has_inner_button show_on_edit"href="/panel/forums/edit/perms/{{$.ID}}-{{.Group.ID}}"><button class='panel_tag'type='submit'>{{lang "panel_forum_full_edit_button"}}</button></a> <a class="panel_right_button has_inner_button show_on_edit"href="/panel/forums/edit/perms/{{$.ID}}-{{.Group.ID}}"><button class='panel_tag'type='submit'>{{lang "panel.forum_full_edit_button"}}</button></a>
</div> </div>
</div> </div>
</div> </div>
{{end}} {{end}}
</div> </div>
{{if .Actions}}
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel.forum_actions_head"}}</h1></div>
</div>
<div id="panel_forum_actions"class="colstack_item rowlist">
{{range .Actions}}
<div class="rowitem panel_compactrow editable_parent">
<a class="panel_upshift">{{.ActionName}}{{if .RunDaysAfterTopicCreation}} - {{.RunDaysAfterTopicCreation}} days after topic creation{{end}}{{if .RunDaysAfterTopicLastReply}} - {{.RunDaysAfterTopicLastReply}} days after topic last reply{{end}}</a>
<span class="panel_floater">
<a href="/panel/forums/action/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}&ret={{$.ID}}"class="panel_tag panel_right_button delete_button"></a>
</span>
</div>
{{end}}
</div>
{{end}}
<div class="colstack_item colstack_head">
<div class="rowitem">
<h1>{{lang "panel.forum_actions_create_head"}}</h1>
</div>
</div>
<div id="panel_forum_action_create"class="colstack_item the_form">
<form action="/panel/forums/action/create/submit/{{.ID}}?s={{.CurrentUser.Session}}"method="post">
<!--<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forum_action_run_on_topic_creation"}}</a></div>
<div class="formitem"><select name="action_run_on_topic_creation">
<option value=1>{{lang "option_yes"}}</option>
<option selected value=0>{{lang "option_no"}}</option>
</select></div>
</div>-->
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forum_action_run_days_after_topic_creation"}}</a></div>
<div class="formitem">
<input name="action_run_days_after_topic_creation"value="0"type="number">
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forum_action_run_days_after_topic_last_reply"}}</a></div>
<div class="formitem">
<input name="action_run_days_after_topic_last_reply"value="0"type="number">
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forum_action_action"}}</a></div>
<div class="formitem">
<select name="action_action">
<option value="delete"selected>{{lang "panel.forum_action_action_delete"}}</option>
<option value="lock">{{lang "panel.forum_action_action_lock"}}</option>
<option value="unlock">{{lang "panel.forum_action_action_unlock"}}</option>
<option value="move">{{lang "panel.forum_action_action_move"}}</option>
</select>
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forum_action_extra"}}</a></div>
<div class="formitem">
<input name="action_extra"type="text">
</div>
</div>
<div class="formrow">
<div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel.forum_action_create_button"}}</button></div>
</div>
</form>
</div>

View File

@ -1,5 +1,5 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div> <div class="rowitem"><h1>{{.Name}}{{lang "panel.forum_head_suffix"}}</h1></div>
</div> </div>
<form action="/panel/forums/edit/perms/adv/submit/{{.ForumID}}-{{.GroupID}}?s={{.CurrentUser.Session}}"method="post"> <form action="/panel/forums/edit/perms/adv/submit/{{.ForumID}}-{{.GroupID}}?s={{.CurrentUser.Session}}"method="post">
<div class="colstack_item rowlist formlist the_form panel_forum_perms"> <div class="colstack_item rowlist formlist the_form panel_forum_perms">
@ -16,7 +16,7 @@
</div> </div>
</div>{{end}} </div>{{end}}
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel_forum_update_button"}}</button></div> <div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel.forum_update_button"}}</button></div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -40,8 +40,8 @@
<div class="formitem"><input name="name" type="text" placeholder="{{lang "panel.forums_create_name"}}"></div> <div class="formitem"><input name="name" type="text" placeholder="{{lang "panel.forums_create_name"}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forums_create_description_label"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forums_create_desc_label"}}</a></div>
<div class="formitem"><input name="desc" type="text" placeholder="{{lang "panel.forums_create_description"}}"></div> <div class="formitem"><input name="desc" type="text" placeholder="{{lang "panel.forums_create_desc"}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forums_active_label"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forums_active_label"}}</a></div>
@ -53,13 +53,13 @@
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel.forums_preset_label"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel.forums_preset_label"}}</a></div>
<div class="formitem"><select name="preset"> <div class="formitem"><select name="preset">
<option selected value="all">{{lang "panel_preset_everyone"}}</option> <option selected value="all">{{lang "panel.preset_everyone"}}</option>
<option value="announce">{{lang "panel_preset_announcements"}}</option> <option value="announce">{{lang "panel.preset_announcements"}}</option>
<option value="members">{{lang "panel_preset_member_only"}}</option> <option value="members">{{lang "panel.preset_member_only"}}</option>
<option value="staff">{{lang "panel_preset_staff_only"}}</option> <option value="staff">{{lang "panel.preset_staff_only"}}</option>
<option value="admins">{{lang "panel_preset_admin_only"}}</option> <option value="admins">{{lang "panel.preset_admin_only"}}</option>
<option value="archive">{{lang "panel_preset_archive"}}</option> <option value="archive">{{lang "panel.preset_archive"}}</option>
<option value="custom">{{lang "panel_preset_custom"}}</option> <option value="custom">{{lang "panel.preset_custom"}}</option>
</select></div> </select></div>
</div> </div>
<div class="formrow"> <div class="formrow">