Added the login log.
Added a better paginator for view topic. The last post on the topic list and forum pages now link to the last page of a topic for quicker navigation. The Account Manager now utilises dyntmpl in more areas. More tooltips. Tweaked the colour for the validated emails in the Email Manager so it looks nicer on dark themes. Moved some inline styles from the Email Manager into the stylesheets and removed obsolete ones. Added the mixed BenchmarkTopicGuestAdminRouteParallelWithRouter benchmark. The bad route benchmark should no longer abort when erroring, something which doesn't make sense as it's always expected to error. Reduce a bit more boilerplate with renderTemplate() Added the *CTemplateSet.addText method and used it to optimise the generated templates a tiny bit more. The forums route now has guest and member variants generated for it. Turned the experimental template optimisation back on for more data. Added the routes.AccountLogins route. Added the account_logins phrase. Added the account_menu_logins phrase. Added the account_logins_head phrase. Added the account_logins_success phrase. Added the account_logins_failure phrase. You need to run the patcher / updater for this commit.
This commit is contained in:
parent
184a7d501e
commit
543ad8a018
|
@ -493,21 +493,18 @@ func createTables(adapter qgen.Adapter) error {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Implement this
|
qgen.Install.CreateTable("login_logs", "", "",
|
||||||
/*
|
[]qgen.DBTableColumn{
|
||||||
qgen.Install.CreateTable("login_logs", "", "",
|
qgen.DBTableColumn{"lid", "int", 0, false, true, ""},
|
||||||
[]qgen.DBTableColumn{
|
qgen.DBTableColumn{"uid", "int", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"lid", "int", 0, false, true, ""},
|
qgen.DBTableColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
||||||
qgen.DBTableColumn{"uid", "int", 0, false, false, ""},
|
qgen.DBTableColumn{"ipaddress", "varchar", 200, false, false, ""},
|
||||||
qgen.DBTableColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
qgen.DBTableColumn{"doneAt", "createdAt", 0, false, false, ""},
|
||||||
qgen.DBTableColumn{"ipaddress", "varchar", 200, false, false, ""},
|
},
|
||||||
qgen.DBTableColumn{"doneAt", "createdAt", 0, false, false, ""},
|
[]qgen.DBTableKey{
|
||||||
},
|
qgen.DBTableKey{"lid", "primary"},
|
||||||
[]qgen.DBTableKey{
|
},
|
||||||
qgen.DBTableKey{"lid", "primary"},
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
qgen.Install.CreateTable("moderation_logs", "", "",
|
qgen.Install.CreateTable("moderation_logs", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]qgen.DBTableColumn{
|
||||||
|
@ -613,12 +610,12 @@ func createTables(adapter qgen.Adapter) error {
|
||||||
[]qgen.DBTableKey{},
|
[]qgen.DBTableKey{},
|
||||||
)
|
)
|
||||||
|
|
||||||
/*qgen.Install.CreateTable("updates", "", "",
|
qgen.Install.CreateTable("updates", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]qgen.DBTableColumn{
|
||||||
qgen.DBTableColumn{"dbVersion", "int", 0, false, false, "0"},
|
qgen.DBTableColumn{"dbVersion", "int", 0, false, false, "0"},
|
||||||
},
|
},
|
||||||
[]qgen.DBTableKey{},
|
[]qgen.DBTableKey{},
|
||||||
)*/
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var RegLogs RegLogStore
|
var RegLogs RegLogStore
|
||||||
|
var LoginLogs LoginLogStore
|
||||||
|
|
||||||
type RegLogItem struct {
|
type RegLogItem struct {
|
||||||
ID int
|
ID int
|
||||||
|
@ -97,3 +98,90 @@ func (store *SQLRegLogStore) GetOffset(offset int, perPage int) (logs []RegLogIt
|
||||||
}
|
}
|
||||||
return logs, rows.Err()
|
return logs, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginLogItem struct {
|
||||||
|
ID int
|
||||||
|
UID int
|
||||||
|
Success bool
|
||||||
|
IPAddress string
|
||||||
|
DoneAt string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginLogStmts struct {
|
||||||
|
update *sql.Stmt
|
||||||
|
create *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginLogStmts LoginLogStmts
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DbInits.Add(func(acc *qgen.Accumulator) error {
|
||||||
|
loginLogStmts = LoginLogStmts{
|
||||||
|
update: acc.Update("login_logs").Set("uid = ?, success = ?").Where("lid = ?").Prepare(),
|
||||||
|
create: acc.Insert("login_logs").Columns("uid, success, ipaddress, doneAt").Fields("?,?,?,UTC_TIMESTAMP()").Prepare(),
|
||||||
|
}
|
||||||
|
return acc.FirstError()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Reload this item in the store, probably doesn't matter right now, but it might when we start caching this stuff in memory
|
||||||
|
// ! Retroactive updates of date are not permitted for integrity reasons
|
||||||
|
func (log *LoginLogItem) Commit() error {
|
||||||
|
_, err := loginLogStmts.update.Exec(log.UID, log.Success, log.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (log *LoginLogItem) Create() (id int, err error) {
|
||||||
|
res, err := loginLogStmts.create.Exec(log.UID, log.Success, log.IPAddress)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
id64, err := res.LastInsertId()
|
||||||
|
log.ID = int(id64)
|
||||||
|
return log.ID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginLogStore interface {
|
||||||
|
GlobalCount() (logCount int)
|
||||||
|
GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SQLLoginLogStore struct {
|
||||||
|
count *sql.Stmt
|
||||||
|
getOffsetByUser *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginLogStore(acc *qgen.Accumulator) (*SQLLoginLogStore, error) {
|
||||||
|
return &SQLLoginLogStore{
|
||||||
|
count: acc.Count("login_logs").Prepare(),
|
||||||
|
getOffsetByUser: acc.Select("login_logs").Columns("lid, success, ipaddress, doneAt").Where("uid = ?").Orderby("doneAt DESC").Limit("?,?").Prepare(),
|
||||||
|
}, acc.FirstError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *SQLLoginLogStore) GlobalCount() (logCount int) {
|
||||||
|
err := store.count.QueryRow().Scan(&logCount)
|
||||||
|
if err != nil {
|
||||||
|
LogError(err)
|
||||||
|
}
|
||||||
|
return logCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *SQLLoginLogStore) GetOffset(uid int, offset int, perPage int) (logs []LoginLogItem, err error) {
|
||||||
|
rows, err := store.getOffsetByUser.Query(uid, offset, perPage)
|
||||||
|
if err != nil {
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var log = LoginLogItem{UID: uid}
|
||||||
|
var doneAt time.Time
|
||||||
|
err := rows.Scan(&log.ID, &log.Success, &log.IPAddress, &doneAt)
|
||||||
|
if err != nil {
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
log.DoneAt = doneAt.Format("2006-01-02 15:04:05")
|
||||||
|
logs = append(logs, log)
|
||||||
|
}
|
||||||
|
return logs, rows.Err()
|
||||||
|
}
|
||||||
|
|
|
@ -104,8 +104,7 @@ type TopicPage struct {
|
||||||
Topic TopicUser
|
Topic TopicUser
|
||||||
Forum *Forum
|
Forum *Forum
|
||||||
Poll Poll
|
Poll Poll
|
||||||
Page int
|
Paginator
|
||||||
LastPage int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TopicListSort struct {
|
type TopicListSort struct {
|
||||||
|
@ -154,16 +153,26 @@ type IPSearchPage struct {
|
||||||
IP string
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
*Header
|
||||||
|
HTMLID string
|
||||||
|
TmplName string
|
||||||
|
Inner nobreak
|
||||||
|
}
|
||||||
|
|
||||||
type EmailListPage struct {
|
type EmailListPage struct {
|
||||||
*Header
|
*Header
|
||||||
ItemList []Email
|
ItemList []Email
|
||||||
Something interface{}
|
}
|
||||||
|
|
||||||
|
type AccountLoginsPage struct {
|
||||||
|
*Header
|
||||||
|
ItemList []LoginLogItem
|
||||||
|
Paginator
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountDashPage struct {
|
type AccountDashPage struct {
|
||||||
*Header
|
*Header
|
||||||
HTMLID string
|
|
||||||
TmplName string
|
|
||||||
MFASetup bool
|
MFASetup bool
|
||||||
CurrentScore int
|
CurrentScore int
|
||||||
NextScore int
|
NextScore int
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -85,6 +84,8 @@ var Template_forums_handle = func(pi ForumsPage, w io.Writer) error {
|
||||||
}
|
}
|
||||||
return Templates.ExecuteTemplate(w, mapping+".html", pi)
|
return Templates.ExecuteTemplate(w, mapping+".html", pi)
|
||||||
}
|
}
|
||||||
|
var Template_forums_guest_handle = Template_forums_handle
|
||||||
|
var Template_forums_member_handle = Template_forums_handle
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
var Template_profile_handle = func(pi ProfilePage, w io.Writer) error {
|
var Template_profile_handle = func(pi ProfilePage, w io.Writer) error {
|
||||||
|
@ -143,7 +144,7 @@ var Template_ip_search_handle = func(pi IPSearchPage, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
var Template_account_handle = func(pi AccountDashPage, w io.Writer) error {
|
var Template_account_handle = func(pi Account, w io.Writer) error {
|
||||||
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["account"]
|
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["account"]
|
||||||
if !ok {
|
if !ok {
|
||||||
mapping = "account"
|
mapping = "account"
|
||||||
|
@ -195,6 +196,8 @@ type TmplLoggedin struct {
|
||||||
Member string
|
Member string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type nobreak interface{}
|
||||||
|
|
||||||
// ? - Add template hooks?
|
// ? - Add template hooks?
|
||||||
func CompileTemplates() error {
|
func CompileTemplates() error {
|
||||||
var config tmpl.CTemplateConfig
|
var config tmpl.CTemplateConfig
|
||||||
|
@ -239,7 +242,7 @@ func CompileTemplates() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Title = "Topic Name"
|
header.Title = "Topic Name"
|
||||||
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, 1, 1}
|
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
|
||||||
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||||
topicTmpl, err := compile("topic", "common.TopicPage", tpage)
|
topicTmpl, err := compile("topic", "common.TopicPage", tpage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -276,13 +279,13 @@ func CompileTemplates() error {
|
||||||
varList = make(map[string]tmpl.VarItem)
|
varList = make(map[string]tmpl.VarItem)
|
||||||
header.Title = "Forum List"
|
header.Title = "Forum List"
|
||||||
forumsPage := ForumsPage{header, forumList}
|
forumsPage := ForumsPage{header, forumList}
|
||||||
forumsTmpl, err := compile("forums", "common.ForumsPage", forumsPage)
|
forumsTmpl, err := compileByLoggedin("forums", "common.ForumsPage", forumsPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var topicsList []*TopicsRow
|
var topicsList []*TopicsRow
|
||||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
|
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
|
||||||
header2.Title = "Topic List"
|
header2.Title = "Topic List"
|
||||||
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
||||||
/*topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage)
|
/*topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage)
|
||||||
|
@ -332,14 +335,9 @@ func CompileTemplates() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mfaSetup := false
|
var inter nobreak
|
||||||
prevScore := GetLevelScore(header.CurrentUser.Level)
|
accountPage := Account{header, "dashboard", "account_own_edit", inter}
|
||||||
currentScore := header.CurrentUser.Score - prevScore
|
accountTmpl, err := compile("account", "common.Account", accountPage)
|
||||||
nextScore := GetLevelScore(header.CurrentUser.Level+1) - prevScore
|
|
||||||
perc := int(math.Ceil((float64(nextScore) / float64(currentScore)) * 100))
|
|
||||||
|
|
||||||
accountPage := AccountDashPage{header, "dashboard", "account_own_edit", mfaSetup, currentScore, nextScore, user.Level + 1, perc * 2}
|
|
||||||
accountTmpl, err := compile("account", "common.AccountDashPage", accountPage)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -441,7 +439,7 @@ func CompileJSTemplates() error {
|
||||||
// TODO: Fix the import loop so we don't have to use this hack anymore
|
// TODO: Fix the import loop so we don't have to use this hack anymore
|
||||||
c.SetBuildTags("!no_templategen,tmplgentopic")
|
c.SetBuildTags("!no_templategen,tmplgentopic")
|
||||||
|
|
||||||
var topicsRow = &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}
|
var topicsRow = &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, "Date", user3.ID, 1, "", "127.0.0.1", 1, 0, 1, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"}
|
||||||
topicListItemTmpl, err := c.Compile("topics_topic.html", "templates/", "*common.TopicsRow", topicsRow, varList)
|
topicListItemTmpl, err := c.Compile("topics_topic.html", "templates/", "*common.TopicsRow", topicsRow, varList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -460,7 +458,7 @@ func CompileJSTemplates() error {
|
||||||
|
|
||||||
varList = make(map[string]tmpl.VarItem)
|
varList = make(map[string]tmpl.VarItem)
|
||||||
header.Title = "Topic Name"
|
header.Title = "Topic Name"
|
||||||
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, 1, 1}
|
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
|
||||||
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||||
topicPostsTmpl, err := c.Compile("topic_posts.html", "templates/", "common.TopicPage", tpage, varList)
|
topicPostsTmpl, err := c.Compile("topic_posts.html", "templates/", "common.TopicPage", tpage, varList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -509,16 +507,16 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string)
|
||||||
}
|
}
|
||||||
getterstr += "}\nreturn nil\n}\n"
|
getterstr += "}\nreturn nil\n}\n"
|
||||||
out += "\n// nolint\nfunc init() {\n"
|
out += "\n// nolint\nfunc init() {\n"
|
||||||
//var bodyMap = make(map[string]string) //map[body]fragmentPrefix
|
var bodyMap = make(map[string]string) //map[body]fragmentPrefix
|
||||||
var tmplMap = make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
|
//var tmplMap = make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
|
||||||
var tmpCount = 0
|
var tmpCount = 0
|
||||||
for _, frag := range c.FragOut {
|
for _, frag := range c.FragOut {
|
||||||
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
|
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
|
||||||
bodyMap, tok := tmplMap[frag.TmplName]
|
/*bodyMap, tok := tmplMap[frag.TmplName]
|
||||||
if !tok {
|
if !tok {
|
||||||
tmplMap[frag.TmplName] = make(map[string]string)
|
tmplMap[frag.TmplName] = make(map[string]string)
|
||||||
bodyMap = tmplMap[frag.TmplName]
|
bodyMap = tmplMap[frag.TmplName]
|
||||||
}
|
}*/
|
||||||
fp, ok := bodyMap[frag.Body]
|
fp, ok := bodyMap[frag.Body]
|
||||||
if !ok {
|
if !ok {
|
||||||
bodyMap[frag.Body] = front
|
bodyMap[frag.Body] = front
|
||||||
|
|
|
@ -520,21 +520,25 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
||||||
case *parse.TemplateNode:
|
case *parse.TemplateNode:
|
||||||
c.compileSubTemplate(con, node)
|
c.compileSubTemplate(con, node)
|
||||||
case *parse.TextNode:
|
case *parse.TextNode:
|
||||||
tmpText := bytes.TrimSpace(node.Text)
|
c.addText(con, node.Text)
|
||||||
if len(tmpText) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nodeText := string(node.Text)
|
|
||||||
fragIndex := c.fragmentCursor[con.TemplateName]
|
|
||||||
_, ok := c.FragOnce[con.TemplateName]
|
|
||||||
c.fragBuf = append(c.fragBuf, Fragment{nodeText, con.TemplateName, fragIndex, ok})
|
|
||||||
con.PushText(strconv.Itoa(fragIndex), fragIndex, len(c.fragBuf)-1)
|
|
||||||
c.fragmentCursor[con.TemplateName] = fragIndex + 1
|
|
||||||
default:
|
default:
|
||||||
c.unknownNode(node)
|
c.unknownNode(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CTemplateSet) addText(con CContext, text []byte) {
|
||||||
|
tmpText := bytes.TrimSpace(text)
|
||||||
|
if len(tmpText) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodeText := string(text)
|
||||||
|
fragIndex := c.fragmentCursor[con.TemplateName]
|
||||||
|
_, ok := c.FragOnce[con.TemplateName]
|
||||||
|
c.fragBuf = append(c.fragBuf, Fragment{nodeText, con.TemplateName, fragIndex, ok})
|
||||||
|
con.PushText(strconv.Itoa(fragIndex), fragIndex, len(c.fragBuf)-1)
|
||||||
|
c.fragmentCursor[con.TemplateName] = fragIndex + 1
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
||||||
c.dumpCall("compileRangeNode", con, node)
|
c.dumpCall("compileRangeNode", con, node)
|
||||||
defer c.retCall("compileRangeNode")
|
defer c.retCall("compileRangeNode")
|
||||||
|
@ -1050,11 +1054,14 @@ func (c *CTemplateSet) compileIfVarSub(con CContext, varname string) (out string
|
||||||
}
|
}
|
||||||
|
|
||||||
var stepInterface = func() {
|
var stepInterface = func() {
|
||||||
if cur.Kind() == reflect.Interface {
|
var nobreak = (cur.Type().Name() == "nobreak")
|
||||||
|
c.detailf("cur.Type().Name(): %+v\n", cur.Type().Name())
|
||||||
|
if cur.Kind() == reflect.Interface && !nobreak {
|
||||||
cur = cur.Elem()
|
cur = cur.Elem()
|
||||||
out += ".(" + cur.Type().Name() + ")"
|
out += ".(" + cur.Type().Name() + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bits := strings.Split(varname, ".")
|
bits := strings.Split(varname, ".")
|
||||||
if varname[0] == '$' {
|
if varname[0] == '$' {
|
||||||
var res VarItemReflect
|
var res VarItemReflect
|
||||||
|
@ -1270,30 +1277,30 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
||||||
c.detail("optimising away member branch")
|
c.detail("optimising away member branch")
|
||||||
if inSlice(userExprs, varname) {
|
if inSlice(userExprs, varname) {
|
||||||
c.detail("positive conditional:", varname)
|
c.detail("positive conditional:", varname)
|
||||||
con.Push("varsub", "[]byte(\"false\")")
|
c.addText(con, []byte("false"))
|
||||||
return
|
return
|
||||||
} else if inSlice(negUserExprs, varname) {
|
} else if inSlice(negUserExprs, varname) {
|
||||||
c.detail("negative conditional:", varname)
|
c.detail("negative conditional:", varname)
|
||||||
con.Push("varsub", "[]byte(\"true\")")
|
c.addText(con, []byte("true"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if c.memberOnly {
|
} else if c.memberOnly {
|
||||||
c.detail("optimising away guest branch")
|
c.detail("optimising away guest branch")
|
||||||
if (con.RootHolder + ".CurrentUser.Loggedin") == varname {
|
if (con.RootHolder + ".CurrentUser.Loggedin") == varname {
|
||||||
c.detail("positive conditional:", varname)
|
c.detail("positive conditional:", varname)
|
||||||
con.Push("varsub", "[]byte(\"true\")")
|
c.addText(con, []byte("true"))
|
||||||
return
|
return
|
||||||
} else if ("!" + con.RootHolder + ".CurrentUser.Loggedin") == varname {
|
} else if ("!" + con.RootHolder + ".CurrentUser.Loggedin") == varname {
|
||||||
c.detail("negative conditional:", varname)
|
c.detail("negative conditional:", varname)
|
||||||
con.Push("varsub", "[]byte(\"false\")")
|
c.addText(con, []byte("false"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
con.Push("startif", "if "+varname+" {\n")
|
con.Push("startif", "if "+varname+" {\n")
|
||||||
con.Push("varsub", "[]byte(\"true\")")
|
c.addText(con, []byte("true"))
|
||||||
con.Push("endif", "} ")
|
con.Push("endif", "} ")
|
||||||
con.Push("startelse", "else {\n")
|
con.Push("startelse", "else {\n")
|
||||||
con.Push("varsub", "[]byte(\"false\")")
|
c.addText(con, []byte("false"))
|
||||||
con.Push("endelse", "}\n")
|
con.Push("endelse", "}\n")
|
||||||
return
|
return
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
|
|
|
@ -390,9 +390,9 @@ func (theme *Theme) RunTmpl(template string, pi interface{}, w io.Writer) error
|
||||||
case *func(IPSearchPage, io.Writer) error:
|
case *func(IPSearchPage, io.Writer) error:
|
||||||
var tmpl = *tmplO
|
var tmpl = *tmplO
|
||||||
return tmpl(pi.(IPSearchPage), w)
|
return tmpl(pi.(IPSearchPage), w)
|
||||||
case *func(AccountDashPage, io.Writer) error:
|
case *func(Account, io.Writer) error:
|
||||||
var tmpl = *tmplO
|
var tmpl = *tmplO
|
||||||
return tmpl(pi.(AccountDashPage), w)
|
return tmpl(pi.(Account), w)
|
||||||
case *func(ErrorPage, io.Writer) error:
|
case *func(ErrorPage, io.Writer) error:
|
||||||
var tmpl = *tmplO
|
var tmpl = *tmplO
|
||||||
return tmpl(pi.(ErrorPage), w)
|
return tmpl(pi.(ErrorPage), w)
|
||||||
|
@ -415,8 +415,8 @@ func (theme *Theme) RunTmpl(template string, pi interface{}, w io.Writer) error
|
||||||
return tmplO(pi.(CreateTopicPage), w)
|
return tmplO(pi.(CreateTopicPage), w)
|
||||||
case func(IPSearchPage, io.Writer) error:
|
case func(IPSearchPage, io.Writer) error:
|
||||||
return tmplO(pi.(IPSearchPage), w)
|
return tmplO(pi.(IPSearchPage), w)
|
||||||
case func(AccountDashPage, io.Writer) error:
|
case func(Account, io.Writer) error:
|
||||||
return tmplO(pi.(AccountDashPage), w)
|
return tmplO(pi.(Account), w)
|
||||||
case func(ErrorPage, io.Writer) error:
|
case func(ErrorPage, io.Writer) error:
|
||||||
return tmplO(pi.(ErrorPage), w)
|
return tmplO(pi.(ErrorPage), w)
|
||||||
case func(Page, io.Writer) error:
|
case func(Page, io.Writer) error:
|
||||||
|
|
|
@ -102,6 +102,7 @@ type TopicsRow struct {
|
||||||
ViewCount int64
|
ViewCount int64
|
||||||
PostCount int
|
PostCount int
|
||||||
LikeCount int
|
LikeCount int
|
||||||
|
LastPage int
|
||||||
ClassName string
|
ClassName string
|
||||||
Data string // Used for report metadata
|
Data string // Used for report metadata
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,9 @@ func (tList *DefaultTopicList) getList(page int, orderby string, argList []inter
|
||||||
|
|
||||||
//topicItem.RelativeCreatedAt = RelativeTime(topicItem.CreatedAt)
|
//topicItem.RelativeCreatedAt = RelativeTime(topicItem.CreatedAt)
|
||||||
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
|
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
|
||||||
|
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
||||||
|
_, _, lastPage := PageOffset(topicItem.PostCount, 1, Config.ItemsPerPage)
|
||||||
|
topicItem.LastPage = lastPage
|
||||||
|
|
||||||
// TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/
|
// TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/
|
||||||
GetHookTable().Vhook("topics_topic_row_assign", &topicItem, &forum)
|
GetHookTable().Vhook("topics_topic_row_assign", &topicItem, &forum)
|
||||||
|
|
243
gen_router.go
243
gen_router.go
|
@ -112,6 +112,7 @@ var RouteMap = map[string]interface{}{
|
||||||
"routes.AccountEditMFADisableSubmit": routes.AccountEditMFADisableSubmit,
|
"routes.AccountEditMFADisableSubmit": routes.AccountEditMFADisableSubmit,
|
||||||
"routes.AccountEditEmail": routes.AccountEditEmail,
|
"routes.AccountEditEmail": routes.AccountEditEmail,
|
||||||
"routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit,
|
"routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit,
|
||||||
|
"routes.AccountLogins": routes.AccountLogins,
|
||||||
"routes.LevelList": routes.LevelList,
|
"routes.LevelList": routes.LevelList,
|
||||||
"routes.ViewProfile": routes.ViewProfile,
|
"routes.ViewProfile": routes.ViewProfile,
|
||||||
"routes.BanUserSubmit": routes.BanUserSubmit,
|
"routes.BanUserSubmit": routes.BanUserSubmit,
|
||||||
|
@ -244,44 +245,45 @@ var routeMapEnum = map[string]int{
|
||||||
"routes.AccountEditMFADisableSubmit": 87,
|
"routes.AccountEditMFADisableSubmit": 87,
|
||||||
"routes.AccountEditEmail": 88,
|
"routes.AccountEditEmail": 88,
|
||||||
"routes.AccountEditEmailTokenSubmit": 89,
|
"routes.AccountEditEmailTokenSubmit": 89,
|
||||||
"routes.LevelList": 90,
|
"routes.AccountLogins": 90,
|
||||||
"routes.ViewProfile": 91,
|
"routes.LevelList": 91,
|
||||||
"routes.BanUserSubmit": 92,
|
"routes.ViewProfile": 92,
|
||||||
"routes.UnbanUser": 93,
|
"routes.BanUserSubmit": 93,
|
||||||
"routes.ActivateUser": 94,
|
"routes.UnbanUser": 94,
|
||||||
"routes.IPSearch": 95,
|
"routes.ActivateUser": 95,
|
||||||
"routes.CreateTopicSubmit": 96,
|
"routes.IPSearch": 96,
|
||||||
"routes.EditTopicSubmit": 97,
|
"routes.CreateTopicSubmit": 97,
|
||||||
"routes.DeleteTopicSubmit": 98,
|
"routes.EditTopicSubmit": 98,
|
||||||
"routes.StickTopicSubmit": 99,
|
"routes.DeleteTopicSubmit": 99,
|
||||||
"routes.UnstickTopicSubmit": 100,
|
"routes.StickTopicSubmit": 100,
|
||||||
"routes.LockTopicSubmit": 101,
|
"routes.UnstickTopicSubmit": 101,
|
||||||
"routes.UnlockTopicSubmit": 102,
|
"routes.LockTopicSubmit": 102,
|
||||||
"routes.MoveTopicSubmit": 103,
|
"routes.UnlockTopicSubmit": 103,
|
||||||
"routes.LikeTopicSubmit": 104,
|
"routes.MoveTopicSubmit": 104,
|
||||||
"routes.ViewTopic": 105,
|
"routes.LikeTopicSubmit": 105,
|
||||||
"routes.CreateReplySubmit": 106,
|
"routes.ViewTopic": 106,
|
||||||
"routes.ReplyEditSubmit": 107,
|
"routes.CreateReplySubmit": 107,
|
||||||
"routes.ReplyDeleteSubmit": 108,
|
"routes.ReplyEditSubmit": 108,
|
||||||
"routes.ReplyLikeSubmit": 109,
|
"routes.ReplyDeleteSubmit": 109,
|
||||||
"routes.ProfileReplyCreateSubmit": 110,
|
"routes.ReplyLikeSubmit": 110,
|
||||||
"routes.ProfileReplyEditSubmit": 111,
|
"routes.ProfileReplyCreateSubmit": 111,
|
||||||
"routes.ProfileReplyDeleteSubmit": 112,
|
"routes.ProfileReplyEditSubmit": 112,
|
||||||
"routes.PollVote": 113,
|
"routes.ProfileReplyDeleteSubmit": 113,
|
||||||
"routes.PollResults": 114,
|
"routes.PollVote": 114,
|
||||||
"routes.AccountLogin": 115,
|
"routes.PollResults": 115,
|
||||||
"routes.AccountRegister": 116,
|
"routes.AccountLogin": 116,
|
||||||
"routes.AccountLogout": 117,
|
"routes.AccountRegister": 117,
|
||||||
"routes.AccountLoginSubmit": 118,
|
"routes.AccountLogout": 118,
|
||||||
"routes.AccountLoginMFAVerify": 119,
|
"routes.AccountLoginSubmit": 119,
|
||||||
"routes.AccountLoginMFAVerifySubmit": 120,
|
"routes.AccountLoginMFAVerify": 120,
|
||||||
"routes.AccountRegisterSubmit": 121,
|
"routes.AccountLoginMFAVerifySubmit": 121,
|
||||||
"routes.DynamicRoute": 122,
|
"routes.AccountRegisterSubmit": 122,
|
||||||
"routes.UploadedFile": 123,
|
"routes.DynamicRoute": 123,
|
||||||
"routes.StaticFile": 124,
|
"routes.UploadedFile": 124,
|
||||||
"routes.RobotsTxt": 125,
|
"routes.StaticFile": 125,
|
||||||
"routes.SitemapXml": 126,
|
"routes.RobotsTxt": 126,
|
||||||
"routes.BadRoute": 127,
|
"routes.SitemapXml": 127,
|
||||||
|
"routes.BadRoute": 128,
|
||||||
}
|
}
|
||||||
var reverseRouteMapEnum = map[int]string{
|
var reverseRouteMapEnum = map[int]string{
|
||||||
0: "routes.Overview",
|
0: "routes.Overview",
|
||||||
|
@ -374,44 +376,45 @@ var reverseRouteMapEnum = map[int]string{
|
||||||
87: "routes.AccountEditMFADisableSubmit",
|
87: "routes.AccountEditMFADisableSubmit",
|
||||||
88: "routes.AccountEditEmail",
|
88: "routes.AccountEditEmail",
|
||||||
89: "routes.AccountEditEmailTokenSubmit",
|
89: "routes.AccountEditEmailTokenSubmit",
|
||||||
90: "routes.LevelList",
|
90: "routes.AccountLogins",
|
||||||
91: "routes.ViewProfile",
|
91: "routes.LevelList",
|
||||||
92: "routes.BanUserSubmit",
|
92: "routes.ViewProfile",
|
||||||
93: "routes.UnbanUser",
|
93: "routes.BanUserSubmit",
|
||||||
94: "routes.ActivateUser",
|
94: "routes.UnbanUser",
|
||||||
95: "routes.IPSearch",
|
95: "routes.ActivateUser",
|
||||||
96: "routes.CreateTopicSubmit",
|
96: "routes.IPSearch",
|
||||||
97: "routes.EditTopicSubmit",
|
97: "routes.CreateTopicSubmit",
|
||||||
98: "routes.DeleteTopicSubmit",
|
98: "routes.EditTopicSubmit",
|
||||||
99: "routes.StickTopicSubmit",
|
99: "routes.DeleteTopicSubmit",
|
||||||
100: "routes.UnstickTopicSubmit",
|
100: "routes.StickTopicSubmit",
|
||||||
101: "routes.LockTopicSubmit",
|
101: "routes.UnstickTopicSubmit",
|
||||||
102: "routes.UnlockTopicSubmit",
|
102: "routes.LockTopicSubmit",
|
||||||
103: "routes.MoveTopicSubmit",
|
103: "routes.UnlockTopicSubmit",
|
||||||
104: "routes.LikeTopicSubmit",
|
104: "routes.MoveTopicSubmit",
|
||||||
105: "routes.ViewTopic",
|
105: "routes.LikeTopicSubmit",
|
||||||
106: "routes.CreateReplySubmit",
|
106: "routes.ViewTopic",
|
||||||
107: "routes.ReplyEditSubmit",
|
107: "routes.CreateReplySubmit",
|
||||||
108: "routes.ReplyDeleteSubmit",
|
108: "routes.ReplyEditSubmit",
|
||||||
109: "routes.ReplyLikeSubmit",
|
109: "routes.ReplyDeleteSubmit",
|
||||||
110: "routes.ProfileReplyCreateSubmit",
|
110: "routes.ReplyLikeSubmit",
|
||||||
111: "routes.ProfileReplyEditSubmit",
|
111: "routes.ProfileReplyCreateSubmit",
|
||||||
112: "routes.ProfileReplyDeleteSubmit",
|
112: "routes.ProfileReplyEditSubmit",
|
||||||
113: "routes.PollVote",
|
113: "routes.ProfileReplyDeleteSubmit",
|
||||||
114: "routes.PollResults",
|
114: "routes.PollVote",
|
||||||
115: "routes.AccountLogin",
|
115: "routes.PollResults",
|
||||||
116: "routes.AccountRegister",
|
116: "routes.AccountLogin",
|
||||||
117: "routes.AccountLogout",
|
117: "routes.AccountRegister",
|
||||||
118: "routes.AccountLoginSubmit",
|
118: "routes.AccountLogout",
|
||||||
119: "routes.AccountLoginMFAVerify",
|
119: "routes.AccountLoginSubmit",
|
||||||
120: "routes.AccountLoginMFAVerifySubmit",
|
120: "routes.AccountLoginMFAVerify",
|
||||||
121: "routes.AccountRegisterSubmit",
|
121: "routes.AccountLoginMFAVerifySubmit",
|
||||||
122: "routes.DynamicRoute",
|
122: "routes.AccountRegisterSubmit",
|
||||||
123: "routes.UploadedFile",
|
123: "routes.DynamicRoute",
|
||||||
124: "routes.StaticFile",
|
124: "routes.UploadedFile",
|
||||||
125: "routes.RobotsTxt",
|
125: "routes.StaticFile",
|
||||||
126: "routes.SitemapXml",
|
126: "routes.RobotsTxt",
|
||||||
127: "routes.BadRoute",
|
127: "routes.SitemapXml",
|
||||||
|
128: "routes.BadRoute",
|
||||||
}
|
}
|
||||||
var osMapEnum = map[string]int{
|
var osMapEnum = map[string]int{
|
||||||
"unknown": 0,
|
"unknown": 0,
|
||||||
|
@ -702,7 +705,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
counters.GlobalViewCounter.Bump()
|
counters.GlobalViewCounter.Bump()
|
||||||
|
|
||||||
if prefix == "/static" {
|
if prefix == "/static" {
|
||||||
counters.RouteViewCounter.Bump(124)
|
counters.RouteViewCounter.Bump(125)
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
routes.StaticFile(w, req)
|
routes.StaticFile(w, req)
|
||||||
return
|
return
|
||||||
|
@ -1567,7 +1570,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(89)
|
counters.RouteViewCounter.Bump(89)
|
||||||
err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData)
|
err = routes.AccountEditEmailTokenSubmit(w,req,user,extraData)
|
||||||
case "/user/levels/":
|
case "/user/edit/logins/":
|
||||||
err = common.MemberOnly(w,req,user)
|
err = common.MemberOnly(w,req,user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1575,13 +1578,25 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(90)
|
counters.RouteViewCounter.Bump(90)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = routes.AccountLogins(w,req,user,head)
|
||||||
|
case "/user/levels/":
|
||||||
|
err = common.MemberOnly(w,req,user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
counters.RouteViewCounter.Bump(91)
|
||||||
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = routes.LevelList(w,req,user,head)
|
err = routes.LevelList(w,req,user,head)
|
||||||
default:
|
default:
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
counters.RouteViewCounter.Bump(91)
|
counters.RouteViewCounter.Bump(92)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1601,7 +1616,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(92)
|
counters.RouteViewCounter.Bump(93)
|
||||||
err = routes.BanUserSubmit(w,req,user,extraData)
|
err = routes.BanUserSubmit(w,req,user,extraData)
|
||||||
case "/users/unban/":
|
case "/users/unban/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1614,7 +1629,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(93)
|
counters.RouteViewCounter.Bump(94)
|
||||||
err = routes.UnbanUser(w,req,user,extraData)
|
err = routes.UnbanUser(w,req,user,extraData)
|
||||||
case "/users/activate/":
|
case "/users/activate/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1627,7 +1642,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(94)
|
counters.RouteViewCounter.Bump(95)
|
||||||
err = routes.ActivateUser(w,req,user,extraData)
|
err = routes.ActivateUser(w,req,user,extraData)
|
||||||
case "/users/ips/":
|
case "/users/ips/":
|
||||||
err = common.MemberOnly(w,req,user)
|
err = common.MemberOnly(w,req,user)
|
||||||
|
@ -1635,7 +1650,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(95)
|
counters.RouteViewCounter.Bump(96)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1659,7 +1674,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(96)
|
counters.RouteViewCounter.Bump(97)
|
||||||
err = routes.CreateTopicSubmit(w,req,user)
|
err = routes.CreateTopicSubmit(w,req,user)
|
||||||
case "/topic/edit/submit/":
|
case "/topic/edit/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1672,7 +1687,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(97)
|
counters.RouteViewCounter.Bump(98)
|
||||||
err = routes.EditTopicSubmit(w,req,user,extraData)
|
err = routes.EditTopicSubmit(w,req,user,extraData)
|
||||||
case "/topic/delete/submit/":
|
case "/topic/delete/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1686,7 +1701,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
}
|
}
|
||||||
|
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
counters.RouteViewCounter.Bump(98)
|
counters.RouteViewCounter.Bump(99)
|
||||||
err = routes.DeleteTopicSubmit(w,req,user)
|
err = routes.DeleteTopicSubmit(w,req,user)
|
||||||
case "/topic/stick/submit/":
|
case "/topic/stick/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1699,7 +1714,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(99)
|
counters.RouteViewCounter.Bump(100)
|
||||||
err = routes.StickTopicSubmit(w,req,user,extraData)
|
err = routes.StickTopicSubmit(w,req,user,extraData)
|
||||||
case "/topic/unstick/submit/":
|
case "/topic/unstick/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1712,7 +1727,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(100)
|
counters.RouteViewCounter.Bump(101)
|
||||||
err = routes.UnstickTopicSubmit(w,req,user,extraData)
|
err = routes.UnstickTopicSubmit(w,req,user,extraData)
|
||||||
case "/topic/lock/submit/":
|
case "/topic/lock/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1726,7 +1741,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
}
|
}
|
||||||
|
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
counters.RouteViewCounter.Bump(101)
|
counters.RouteViewCounter.Bump(102)
|
||||||
err = routes.LockTopicSubmit(w,req,user)
|
err = routes.LockTopicSubmit(w,req,user)
|
||||||
case "/topic/unlock/submit/":
|
case "/topic/unlock/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1739,7 +1754,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(102)
|
counters.RouteViewCounter.Bump(103)
|
||||||
err = routes.UnlockTopicSubmit(w,req,user,extraData)
|
err = routes.UnlockTopicSubmit(w,req,user,extraData)
|
||||||
case "/topic/move/submit/":
|
case "/topic/move/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1752,7 +1767,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(103)
|
counters.RouteViewCounter.Bump(104)
|
||||||
err = routes.MoveTopicSubmit(w,req,user,extraData)
|
err = routes.MoveTopicSubmit(w,req,user,extraData)
|
||||||
case "/topic/like/submit/":
|
case "/topic/like/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1770,10 +1785,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(104)
|
counters.RouteViewCounter.Bump(105)
|
||||||
err = routes.LikeTopicSubmit(w,req,user,extraData)
|
err = routes.LikeTopicSubmit(w,req,user,extraData)
|
||||||
default:
|
default:
|
||||||
counters.RouteViewCounter.Bump(105)
|
counters.RouteViewCounter.Bump(106)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1797,7 +1812,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(106)
|
counters.RouteViewCounter.Bump(107)
|
||||||
err = routes.CreateReplySubmit(w,req,user)
|
err = routes.CreateReplySubmit(w,req,user)
|
||||||
case "/reply/edit/submit/":
|
case "/reply/edit/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1810,7 +1825,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(107)
|
counters.RouteViewCounter.Bump(108)
|
||||||
err = routes.ReplyEditSubmit(w,req,user,extraData)
|
err = routes.ReplyEditSubmit(w,req,user,extraData)
|
||||||
case "/reply/delete/submit/":
|
case "/reply/delete/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1823,7 +1838,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(108)
|
counters.RouteViewCounter.Bump(109)
|
||||||
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
|
err = routes.ReplyDeleteSubmit(w,req,user,extraData)
|
||||||
case "/reply/like/submit/":
|
case "/reply/like/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1841,7 +1856,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(109)
|
counters.RouteViewCounter.Bump(110)
|
||||||
err = routes.ReplyLikeSubmit(w,req,user,extraData)
|
err = routes.ReplyLikeSubmit(w,req,user,extraData)
|
||||||
}
|
}
|
||||||
case "/profile":
|
case "/profile":
|
||||||
|
@ -1857,7 +1872,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(110)
|
counters.RouteViewCounter.Bump(111)
|
||||||
err = routes.ProfileReplyCreateSubmit(w,req,user)
|
err = routes.ProfileReplyCreateSubmit(w,req,user)
|
||||||
case "/profile/reply/edit/submit/":
|
case "/profile/reply/edit/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1870,7 +1885,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(111)
|
counters.RouteViewCounter.Bump(112)
|
||||||
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
|
err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
|
||||||
case "/profile/reply/delete/submit/":
|
case "/profile/reply/delete/submit/":
|
||||||
err = common.NoSessionMismatch(w,req,user)
|
err = common.NoSessionMismatch(w,req,user)
|
||||||
|
@ -1883,7 +1898,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(112)
|
counters.RouteViewCounter.Bump(113)
|
||||||
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
|
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
|
||||||
}
|
}
|
||||||
case "/poll":
|
case "/poll":
|
||||||
|
@ -1899,23 +1914,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(113)
|
counters.RouteViewCounter.Bump(114)
|
||||||
err = routes.PollVote(w,req,user,extraData)
|
err = routes.PollVote(w,req,user,extraData)
|
||||||
case "/poll/results/":
|
case "/poll/results/":
|
||||||
counters.RouteViewCounter.Bump(114)
|
counters.RouteViewCounter.Bump(115)
|
||||||
err = routes.PollResults(w,req,user,extraData)
|
err = routes.PollResults(w,req,user,extraData)
|
||||||
}
|
}
|
||||||
case "/accounts":
|
case "/accounts":
|
||||||
switch(req.URL.Path) {
|
switch(req.URL.Path) {
|
||||||
case "/accounts/login/":
|
case "/accounts/login/":
|
||||||
counters.RouteViewCounter.Bump(115)
|
counters.RouteViewCounter.Bump(116)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = routes.AccountLogin(w,req,user,head)
|
err = routes.AccountLogin(w,req,user,head)
|
||||||
case "/accounts/create/":
|
case "/accounts/create/":
|
||||||
counters.RouteViewCounter.Bump(116)
|
counters.RouteViewCounter.Bump(117)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1932,7 +1947,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(117)
|
counters.RouteViewCounter.Bump(118)
|
||||||
err = routes.AccountLogout(w,req,user)
|
err = routes.AccountLogout(w,req,user)
|
||||||
case "/accounts/login/submit/":
|
case "/accounts/login/submit/":
|
||||||
err = common.ParseForm(w,req,user)
|
err = common.ParseForm(w,req,user)
|
||||||
|
@ -1940,10 +1955,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(118)
|
counters.RouteViewCounter.Bump(119)
|
||||||
err = routes.AccountLoginSubmit(w,req,user)
|
err = routes.AccountLoginSubmit(w,req,user)
|
||||||
case "/accounts/mfa_verify/":
|
case "/accounts/mfa_verify/":
|
||||||
counters.RouteViewCounter.Bump(119)
|
counters.RouteViewCounter.Bump(120)
|
||||||
head, err := common.UserCheck(w,req,&user)
|
head, err := common.UserCheck(w,req,&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1955,7 +1970,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(120)
|
counters.RouteViewCounter.Bump(121)
|
||||||
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
|
err = routes.AccountLoginMFAVerifySubmit(w,req,user)
|
||||||
case "/accounts/create/submit/":
|
case "/accounts/create/submit/":
|
||||||
err = common.ParseForm(w,req,user)
|
err = common.ParseForm(w,req,user)
|
||||||
|
@ -1963,7 +1978,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
counters.RouteViewCounter.Bump(121)
|
counters.RouteViewCounter.Bump(122)
|
||||||
err = routes.AccountRegisterSubmit(w,req,user)
|
err = routes.AccountRegisterSubmit(w,req,user)
|
||||||
}
|
}
|
||||||
/*case "/sitemaps": // TODO: Count these views
|
/*case "/sitemaps": // TODO: Count these views
|
||||||
|
@ -1979,7 +1994,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
w.Header().Del("Content-Encoding")
|
w.Header().Del("Content-Encoding")
|
||||||
}
|
}
|
||||||
counters.RouteViewCounter.Bump(123)
|
counters.RouteViewCounter.Bump(124)
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
// TODO: Find a way to propagate errors up from this?
|
// TODO: Find a way to propagate errors up from this?
|
||||||
r.UploadHandler(w,req) // TODO: Count these views
|
r.UploadHandler(w,req) // TODO: Count these views
|
||||||
|
@ -1989,10 +2004,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
// TODO: Add support for favicons and robots.txt files
|
// TODO: Add support for favicons and robots.txt files
|
||||||
switch(extraData) {
|
switch(extraData) {
|
||||||
case "robots.txt":
|
case "robots.txt":
|
||||||
counters.RouteViewCounter.Bump(125)
|
counters.RouteViewCounter.Bump(126)
|
||||||
return routes.RobotsTxt(w,req)
|
return routes.RobotsTxt(w,req)
|
||||||
/*case "sitemap.xml":
|
/*case "sitemap.xml":
|
||||||
counters.RouteViewCounter.Bump(126)
|
counters.RouteViewCounter.Bump(127)
|
||||||
return routes.SitemapXml(w,req)*/
|
return routes.SitemapXml(w,req)*/
|
||||||
}
|
}
|
||||||
return common.NotFound(w,req,nil)
|
return common.NotFound(w,req,nil)
|
||||||
|
@ -2003,7 +2018,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
r.RUnlock()
|
r.RUnlock()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
counters.RouteViewCounter.Bump(122) // TODO: Be more specific about *which* dynamic route it is
|
counters.RouteViewCounter.Bump(123) // TODO: Be more specific about *which* dynamic route it is
|
||||||
req.URL.Path += extraData
|
req.URL.Path += extraData
|
||||||
return handle(w,req,user)
|
return handle(w,req,user)
|
||||||
}
|
}
|
||||||
|
@ -2014,7 +2029,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
} else {
|
} else {
|
||||||
r.DumpRequest(req,"Bad Route")
|
r.DumpRequest(req,"Bad Route")
|
||||||
}
|
}
|
||||||
counters.RouteViewCounter.Bump(127)
|
counters.RouteViewCounter.Bump(128)
|
||||||
return common.NotFound(w,req,nil)
|
return common.NotFound(w,req,nil)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -2,22 +2,23 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
var dbTablePrimaryKeys = map[string]string{
|
var dbTablePrimaryKeys = map[string]string{
|
||||||
"attachments":"attachID",
|
"users":"uid",
|
||||||
|
"users_groups":"gid",
|
||||||
|
"users_groups_scheduler":"uid",
|
||||||
|
"polls":"pollID",
|
||||||
|
"registration_logs":"rlid",
|
||||||
|
"activity_stream":"asid",
|
||||||
|
"login_logs":"lid",
|
||||||
|
"users_2fa_keys":"uid",
|
||||||
"topics":"tid",
|
"topics":"tid",
|
||||||
"replies":"rid",
|
"replies":"rid",
|
||||||
"polls":"pollID",
|
"attachments":"attachID",
|
||||||
"users_2fa_keys":"uid",
|
|
||||||
"users_groups_scheduler":"uid",
|
|
||||||
"revisions":"reviseID",
|
"revisions":"reviseID",
|
||||||
"users_replies":"rid",
|
|
||||||
"menus":"mid",
|
|
||||||
"users":"uid",
|
|
||||||
"forums":"fid",
|
|
||||||
"activity_stream":"asid",
|
|
||||||
"word_filters":"wfid",
|
|
||||||
"menu_items":"miid",
|
|
||||||
"pages":"pid",
|
|
||||||
"registration_logs":"rlid",
|
|
||||||
"users_groups":"gid",
|
|
||||||
"users_avatar_queue":"uid",
|
"users_avatar_queue":"uid",
|
||||||
|
"forums":"fid",
|
||||||
|
"menu_items":"miid",
|
||||||
|
"users_replies":"rid",
|
||||||
|
"word_filters":"wfid",
|
||||||
|
"menus":"mid",
|
||||||
|
"pages":"pid",
|
||||||
}
|
}
|
||||||
|
|
106
general_test.go
106
general_test.go
|
@ -175,11 +175,12 @@ func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) {
|
||||||
}
|
}
|
||||||
uidCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year}
|
uidCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year}
|
||||||
sessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year}
|
sessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year}
|
||||||
|
path := "/topic/hm."+benchTid
|
||||||
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
reqAdmin := httptest.NewRequest("get", path, bytes.NewReader(nil))
|
||||||
reqAdmin.AddCookie(&uidCookie)
|
reqAdmin.AddCookie(&uidCookie)
|
||||||
reqAdmin.AddCookie(&sessionCookie)
|
reqAdmin.AddCookie(&sessionCookie)
|
||||||
reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||||
|
@ -209,6 +210,60 @@ func BenchmarkTopicAdminRouteParallelAltAlt(b *testing.B) {
|
||||||
BenchmarkTopicAdminRouteParallel(b)
|
BenchmarkTopicAdminRouteParallel(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkTopicGuestAdminRouteParallelWithRouter(b *testing.B) {
|
||||||
|
binit(b)
|
||||||
|
router, err := NewGenRouter(http.FileServer(http.Dir("./uploads")))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg := NewStashConfig()
|
||||||
|
common.Dev.DebugMode = false
|
||||||
|
common.Dev.SuperDebug = false
|
||||||
|
|
||||||
|
admin, err := common.Users.Get(1)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if !admin.IsAdmin {
|
||||||
|
b.Fatal("UID1 is not an admin")
|
||||||
|
}
|
||||||
|
uidCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year}
|
||||||
|
sessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year}
|
||||||
|
path := "/topic/hm."+benchTid
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
reqAdmin := httptest.NewRequest("get", path, bytes.NewReader(nil))
|
||||||
|
reqAdmin.AddCookie(&uidCookie)
|
||||||
|
reqAdmin.AddCookie(&sessionCookie)
|
||||||
|
reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||||
|
reqAdmin.Header.Set("Host", "localhost")
|
||||||
|
reqAdmin.Host = "localhost"
|
||||||
|
router.ServeHTTP(w, reqAdmin)
|
||||||
|
if w.Code != 200 {
|
||||||
|
b.Log(w.Body)
|
||||||
|
b.Fatal("HTTP Error!")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest("GET", path, bytes.NewReader(nil))
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||||
|
req.Header.Set("Host", "localhost")
|
||||||
|
req.Host = "localhost"
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
if w.Code != 200 {
|
||||||
|
b.Log(w.Body)
|
||||||
|
b.Fatal("HTTP Error!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cfg.Restore()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkTopicGuestRouteParallel(b *testing.B) {
|
func BenchmarkTopicGuestRouteParallel(b *testing.B) {
|
||||||
binit(b)
|
binit(b)
|
||||||
cfg := NewStashConfig()
|
cfg := NewStashConfig()
|
||||||
|
@ -272,6 +327,15 @@ func obRoute(b *testing.B, path string) {
|
||||||
cfg.Restore()
|
cfg.Restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func obRouteNoError(b *testing.B, path string) {
|
||||||
|
binit(b)
|
||||||
|
cfg := NewStashConfig()
|
||||||
|
common.Dev.DebugMode = false
|
||||||
|
common.Dev.SuperDebug = false
|
||||||
|
b.RunParallel(benchRouteNoError(b, path))
|
||||||
|
cfg.Restore()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkTopicsGuestRouteParallelWithRouter(b *testing.B) {
|
func BenchmarkTopicsGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
obRoute(b, "/topics/")
|
obRoute(b, "/topics/")
|
||||||
}
|
}
|
||||||
|
@ -292,10 +356,9 @@ func BenchmarkTopicGuestRouteParallelWithRouterAlt(b *testing.B) {
|
||||||
obRoute(b, "/topic/hm."+benchTid)
|
obRoute(b, "/topic/hm."+benchTid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Needs to stop failing the tests unnecessarily
|
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
/*func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
obRouteNoError(b, "/garble/haa")
|
||||||
obRoute(b, "/garble/haa")
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
// TODO: Alternate between member and guest to bust some CPU caches?
|
// TODO: Alternate between member and guest to bust some CPU caches?
|
||||||
|
|
||||||
|
@ -330,20 +393,37 @@ func benchRoute(b *testing.B, path string) func(*testing.PB) {
|
||||||
}
|
}
|
||||||
return func(pb *testing.PB) {
|
return func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
listW := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
listReq := httptest.NewRequest("GET", path, bytes.NewReader(nil))
|
req := httptest.NewRequest("GET", path, bytes.NewReader(nil))
|
||||||
listReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||||
listReq.Header.Set("Host", "localhost")
|
req.Header.Set("Host", "localhost")
|
||||||
listReq.Host = "localhost"
|
req.Host = "localhost"
|
||||||
router.ServeHTTP(listW, listReq)
|
router.ServeHTTP(w, req)
|
||||||
if listW.Code != 200 {
|
if w.Code != 200 {
|
||||||
b.Log(listW.Body)
|
b.Log(w.Body)
|
||||||
b.Fatal("HTTP Error!")
|
b.Fatal("HTTP Error!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func benchRouteNoError(b *testing.B, path string) func(*testing.PB) {
|
||||||
|
router, err := NewGenRouter(http.FileServer(http.Dir("./uploads")))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
return func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest("GET", path, bytes.NewReader(nil))
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||||
|
req.Header.Set("Host", "localhost")
|
||||||
|
req.Host = "localhost"
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkProfileGuestRouteParallelWithRouter(b *testing.B) {
|
func BenchmarkProfileGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
obRoute(b, "/profile/admin.1")
|
obRoute(b, "/profile/admin.1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,7 @@
|
||||||
"account_mfa":"Manage 2FA",
|
"account_mfa":"Manage 2FA",
|
||||||
"account_mfa_setup":"Setup 2FA",
|
"account_mfa_setup":"Setup 2FA",
|
||||||
"account_email":"Email Manager",
|
"account_email":"Email Manager",
|
||||||
|
"account_logins":"Logins",
|
||||||
"account_level_list":"Level Progress",
|
"account_level_list":"Level Progress",
|
||||||
|
|
||||||
"panel_dashboard":"Control Panel Dashboard",
|
"panel_dashboard":"Control Panel Dashboard",
|
||||||
|
@ -447,6 +448,7 @@
|
||||||
"account_menu_email":"Email",
|
"account_menu_email":"Email",
|
||||||
"account_menu_security":"Security",
|
"account_menu_security":"Security",
|
||||||
"account_menu_notifications":"Notifications",
|
"account_menu_notifications":"Notifications",
|
||||||
|
"account_menu_logins":"Logins",
|
||||||
|
|
||||||
"account_coming_soon":"Coming Soon",
|
"account_coming_soon":"Coming Soon",
|
||||||
|
|
||||||
|
@ -480,6 +482,10 @@
|
||||||
"account_mfa_setup_verify":"Verify",
|
"account_mfa_setup_verify":"Verify",
|
||||||
"account_mfa_setup_button":"Setup",
|
"account_mfa_setup_button":"Setup",
|
||||||
|
|
||||||
|
"account_logins_head":"Logins",
|
||||||
|
"account_logins_success":"Successful Login",
|
||||||
|
"account_logins_failure":"Failed Login",
|
||||||
|
|
||||||
"areyousure_head":"Are you sure?",
|
"areyousure_head":"Are you sure?",
|
||||||
"areyousure_continue":"Continue",
|
"areyousure_continue":"Continue",
|
||||||
|
|
||||||
|
|
4
main.go
4
main.go
|
@ -125,6 +125,10 @@ func afterDBInit() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
common.LoginLogs, err = common.NewLoginLogStore(acc)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
common.RegLogs, err = common.NewRegLogStore(acc)
|
common.RegLogs, err = common.NewRegLogStore(acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
|
|
@ -17,6 +17,7 @@ func init() {
|
||||||
addPatch(6, patch6)
|
addPatch(6, patch6)
|
||||||
addPatch(7, patch7)
|
addPatch(7, patch7)
|
||||||
addPatch(8, patch8)
|
addPatch(8, patch8)
|
||||||
|
addPatch(9, patch9)
|
||||||
}
|
}
|
||||||
|
|
||||||
func patch0(scanner *bufio.Scanner) (err error) {
|
func patch0(scanner *bufio.Scanner) (err error) {
|
||||||
|
@ -134,9 +135,9 @@ func patch0(scanner *bufio.Scanner) (err error) {
|
||||||
func patch1(scanner *bufio.Scanner) error {
|
func patch1(scanner *bufio.Scanner) error {
|
||||||
var routes = map[string]string{
|
var routes = 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)
|
return renameRoutes(routes)
|
||||||
|
@ -144,15 +145,15 @@ func patch1(scanner *bufio.Scanner) error {
|
||||||
|
|
||||||
func patch2(scanner *bufio.Scanner) error {
|
func patch2(scanner *bufio.Scanner) error {
|
||||||
var routes = map[string]string{
|
var routes = map[string]string{
|
||||||
"routeLogout": "routes.AccountLogout",
|
"routeLogout": "routes.AccountLogout",
|
||||||
"routeShowAttachment": "routes.ShowAttachment",
|
"routeShowAttachment": "routes.ShowAttachment",
|
||||||
"routeChangeTheme": "routes.ChangeTheme",
|
"routeChangeTheme": "routes.ChangeTheme",
|
||||||
"routeProfileReplyCreateSubmit": "routes.ProfileReplyCreateSubmit",
|
"routeProfileReplyCreateSubmit": "routes.ProfileReplyCreateSubmit",
|
||||||
"routeLikeTopicSubmit": "routes.LikeTopicSubmit",
|
"routeLikeTopicSubmit": "routes.LikeTopicSubmit",
|
||||||
"routeReplyLikeSubmit": "routes.ReplyLikeSubmit",
|
"routeReplyLikeSubmit": "routes.ReplyLikeSubmit",
|
||||||
"routeDynamic": "routes.DynamicRoute",
|
"routeDynamic": "routes.DynamicRoute",
|
||||||
"routeUploads": "routes.UploadedFile",
|
"routeUploads": "routes.UploadedFile",
|
||||||
"BadRoute": "routes.BadRoute",
|
"BadRoute": "routes.BadRoute",
|
||||||
}
|
}
|
||||||
return renameRoutes(routes)
|
return renameRoutes(routes)
|
||||||
}
|
}
|
||||||
|
@ -181,41 +182,41 @@ func patch3(scanner *bufio.Scanner) error {
|
||||||
|
|
||||||
func patch4(scanner *bufio.Scanner) error {
|
func patch4(scanner *bufio.Scanner) error {
|
||||||
var routes = map[string]string{
|
var routes = map[string]string{
|
||||||
"routeReportSubmit": "routes.ReportSubmit",
|
"routeReportSubmit": "routes.ReportSubmit",
|
||||||
"routeAccountEditEmail": "routes.AccountEditEmail",
|
"routeAccountEditEmail": "routes.AccountEditEmail",
|
||||||
"routeAccountEditEmailTokenSubmit": "routes.AccountEditEmailTokenSubmit",
|
"routeAccountEditEmailTokenSubmit": "routes.AccountEditEmailTokenSubmit",
|
||||||
"routePanelLogsRegs": "panel.LogsRegs",
|
"routePanelLogsRegs": "panel.LogsRegs",
|
||||||
"routePanelLogsMod": "panel.LogsMod",
|
"routePanelLogsMod": "panel.LogsMod",
|
||||||
"routePanelLogsAdmin": "panel.LogsAdmin",
|
"routePanelLogsAdmin": "panel.LogsAdmin",
|
||||||
"routePanelDebug": "panel.Debug",
|
"routePanelDebug": "panel.Debug",
|
||||||
"routePanelAnalyticsViews": "panel.AnalyticsViews",
|
"routePanelAnalyticsViews": "panel.AnalyticsViews",
|
||||||
"routePanelAnalyticsRouteViews": "panel.AnalyticsRouteViews",
|
"routePanelAnalyticsRouteViews": "panel.AnalyticsRouteViews",
|
||||||
"routePanelAnalyticsAgentViews": "panel.AnalyticsAgentViews",
|
"routePanelAnalyticsAgentViews": "panel.AnalyticsAgentViews",
|
||||||
"routePanelAnalyticsForumViews": "panel.AnalyticsForumViews",
|
"routePanelAnalyticsForumViews": "panel.AnalyticsForumViews",
|
||||||
"routePanelAnalyticsSystemViews": "panel.AnalyticsSystemViews",
|
"routePanelAnalyticsSystemViews": "panel.AnalyticsSystemViews",
|
||||||
"routePanelAnalyticsLanguageViews": "panel.AnalyticsLanguageViews",
|
"routePanelAnalyticsLanguageViews": "panel.AnalyticsLanguageViews",
|
||||||
"routePanelAnalyticsReferrerViews": "panel.AnalyticsReferrerViews",
|
"routePanelAnalyticsReferrerViews": "panel.AnalyticsReferrerViews",
|
||||||
"routePanelAnalyticsTopics": "panel.AnalyticsTopics",
|
"routePanelAnalyticsTopics": "panel.AnalyticsTopics",
|
||||||
"routePanelAnalyticsPosts": "panel.AnalyticsPosts",
|
"routePanelAnalyticsPosts": "panel.AnalyticsPosts",
|
||||||
"routePanelAnalyticsForums": "panel.AnalyticsForums",
|
"routePanelAnalyticsForums": "panel.AnalyticsForums",
|
||||||
"routePanelAnalyticsRoutes": "panel.AnalyticsRoutes",
|
"routePanelAnalyticsRoutes": "panel.AnalyticsRoutes",
|
||||||
"routePanelAnalyticsAgents": "panel.AnalyticsAgents",
|
"routePanelAnalyticsAgents": "panel.AnalyticsAgents",
|
||||||
"routePanelAnalyticsSystems": "panel.AnalyticsSystems",
|
"routePanelAnalyticsSystems": "panel.AnalyticsSystems",
|
||||||
"routePanelAnalyticsLanguages": "panel.AnalyticsLanguages",
|
"routePanelAnalyticsLanguages": "panel.AnalyticsLanguages",
|
||||||
"routePanelAnalyticsReferrers": "panel.AnalyticsReferrers",
|
"routePanelAnalyticsReferrers": "panel.AnalyticsReferrers",
|
||||||
"routePanelSettings": "panel.Settings",
|
"routePanelSettings": "panel.Settings",
|
||||||
"routePanelSettingEdit": "panel.SettingEdit",
|
"routePanelSettingEdit": "panel.SettingEdit",
|
||||||
"routePanelSettingEditSubmit": "panel.SettingEditSubmit",
|
"routePanelSettingEditSubmit": "panel.SettingEditSubmit",
|
||||||
"routePanelForums": "panel.Forums",
|
"routePanelForums": "panel.Forums",
|
||||||
"routePanelForumsCreateSubmit": "panel.ForumsCreateSubmit",
|
"routePanelForumsCreateSubmit": "panel.ForumsCreateSubmit",
|
||||||
"routePanelForumsDelete": "panel.ForumsDelete",
|
"routePanelForumsDelete": "panel.ForumsDelete",
|
||||||
"routePanelForumsDeleteSubmit": "panel.ForumsDeleteSubmit",
|
"routePanelForumsDeleteSubmit": "panel.ForumsDeleteSubmit",
|
||||||
"routePanelForumsEdit": "panel.ForumsEdit",
|
"routePanelForumsEdit": "panel.ForumsEdit",
|
||||||
"routePanelForumsEditSubmit": "panel.ForumsEditSubmit",
|
"routePanelForumsEditSubmit": "panel.ForumsEditSubmit",
|
||||||
"routePanelForumsEditPermsSubmit": "panel.ForumsEditPermsSubmit",
|
"routePanelForumsEditPermsSubmit": "panel.ForumsEditPermsSubmit",
|
||||||
"routePanelForumsEditPermsAdvance": "panel.ForumsEditPermsAdvance",
|
"routePanelForumsEditPermsAdvance": "panel.ForumsEditPermsAdvance",
|
||||||
"routePanelForumsEditPermsAdvanceSubmit": "panel.ForumsEditPermsAdvanceSubmit",
|
"routePanelForumsEditPermsAdvanceSubmit": "panel.ForumsEditPermsAdvanceSubmit",
|
||||||
"routePanelBackups": "panel.Backups",
|
"routePanelBackups": "panel.Backups",
|
||||||
}
|
}
|
||||||
err := renameRoutes(routes)
|
err := renameRoutes(routes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -249,10 +250,10 @@ func patch4(scanner *bufio.Scanner) error {
|
||||||
|
|
||||||
func patch5(scanner *bufio.Scanner) error {
|
func patch5(scanner *bufio.Scanner) error {
|
||||||
var routes = map[string]string{
|
var routes = map[string]string{
|
||||||
"routePanelUsers": "panel.Users",
|
"routePanelUsers": "panel.Users",
|
||||||
"routePanelUsersEdit": "panel.UsersEdit",
|
"routePanelUsersEdit": "panel.UsersEdit",
|
||||||
"routePanelUsersEditSubmit": "panel.UsersEditSubmit",
|
"routePanelUsersEditSubmit": "panel.UsersEditSubmit",
|
||||||
"routes.AccountEditCritical": "routes.AccountEditPassword",
|
"routes.AccountEditCritical": "routes.AccountEditPassword",
|
||||||
"routes.AccountEditCriticalSubmit": "routes.AccountEditPasswordSubmit",
|
"routes.AccountEditCriticalSubmit": "routes.AccountEditPasswordSubmit",
|
||||||
}
|
}
|
||||||
err := renameRoutes(routes)
|
err := renameRoutes(routes)
|
||||||
|
@ -322,7 +323,7 @@ func renameRoutes(routes map[string]string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range routes {
|
for key, value := range routes {
|
||||||
err := replaceTextWhere(key,value)
|
err := replaceTextWhere(key, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -333,37 +334,41 @@ func renameRoutes(routes map[string]string) error {
|
||||||
|
|
||||||
func patch8(scanner *bufio.Scanner) error {
|
func patch8(scanner *bufio.Scanner) error {
|
||||||
var routes = map[string]string{
|
var routes = map[string]string{
|
||||||
"routePanelWordFilter": "panel.WordFilters",
|
"routePanelWordFilter": "panel.WordFilters",
|
||||||
"routePanelWordFiltersCreateSubmit": "panel.WordFiltersCreateSubmit",
|
"routePanelWordFiltersCreateSubmit": "panel.WordFiltersCreateSubmit",
|
||||||
"routePanelWordFiltersEdit": "panel.WordFiltersEdit",
|
"routePanelWordFiltersEdit": "panel.WordFiltersEdit",
|
||||||
"routePanelWordFiltersEditSubmit": "panel.WordFiltersEditSubmit",
|
"routePanelWordFiltersEditSubmit": "panel.WordFiltersEditSubmit",
|
||||||
"routePanelWordFiltersDeleteSubmit": "panel.WordFiltersDeleteSubmit",
|
"routePanelWordFiltersDeleteSubmit": "panel.WordFiltersDeleteSubmit",
|
||||||
"routePanelPlugins": "panel.Plugins",
|
"routePanelPlugins": "panel.Plugins",
|
||||||
"routePanelPluginsActivate": "panel.PluginsActivate",
|
"routePanelPluginsActivate": "panel.PluginsActivate",
|
||||||
"routePanelPluginsDeactivate": "panel.PluginsDeactivate",
|
"routePanelPluginsDeactivate": "panel.PluginsDeactivate",
|
||||||
"routePanelPluginsInstall": "panel.PluginsInstall",
|
"routePanelPluginsInstall": "panel.PluginsInstall",
|
||||||
"routePanelGroups": "panel.Groups",
|
"routePanelGroups": "panel.Groups",
|
||||||
"routePanelGroupsEdit":"panel.GroupsEdit",
|
"routePanelGroupsEdit": "panel.GroupsEdit",
|
||||||
"routePanelGroupsEditPerms":"panel.GroupsEditPerms",
|
"routePanelGroupsEditPerms": "panel.GroupsEditPerms",
|
||||||
"routePanelGroupsEditSubmit":"panel.GroupsEditSubmit",
|
"routePanelGroupsEditSubmit": "panel.GroupsEditSubmit",
|
||||||
"routePanelGroupsEditPermsSubmit":"panel.GroupsEditPermsSubmit",
|
"routePanelGroupsEditPermsSubmit": "panel.GroupsEditPermsSubmit",
|
||||||
"routePanelGroupsCreateSubmit":"panel.GroupsCreateSubmit",
|
"routePanelGroupsCreateSubmit": "panel.GroupsCreateSubmit",
|
||||||
"routePanelThemes":"panel.Themes",
|
"routePanelThemes": "panel.Themes",
|
||||||
"routePanelThemesSetDefault":"panel.ThemesSetDefault",
|
"routePanelThemesSetDefault": "panel.ThemesSetDefault",
|
||||||
"routePanelThemesMenus":"panel.ThemesMenus",
|
"routePanelThemesMenus": "panel.ThemesMenus",
|
||||||
"routePanelThemesMenusEdit":"panel.ThemesMenusEdit",
|
"routePanelThemesMenusEdit": "panel.ThemesMenusEdit",
|
||||||
"routePanelThemesMenuItemEdit":"panel.ThemesMenuItemEdit",
|
"routePanelThemesMenuItemEdit": "panel.ThemesMenuItemEdit",
|
||||||
"routePanelThemesMenuItemEditSubmit":"panel.ThemesMenuItemEditSubmit",
|
"routePanelThemesMenuItemEditSubmit": "panel.ThemesMenuItemEditSubmit",
|
||||||
"routePanelThemesMenuItemCreateSubmit":"panel.ThemesMenuItemCreateSubmit",
|
"routePanelThemesMenuItemCreateSubmit": "panel.ThemesMenuItemCreateSubmit",
|
||||||
"routePanelThemesMenuItemDeleteSubmit":"panel.ThemesMenuItemDeleteSubmit",
|
"routePanelThemesMenuItemDeleteSubmit": "panel.ThemesMenuItemDeleteSubmit",
|
||||||
"routePanelThemesMenuItemOrderSubmit":"panel.ThemesMenuItemOrderSubmit",
|
"routePanelThemesMenuItemOrderSubmit": "panel.ThemesMenuItemOrderSubmit",
|
||||||
"routePanelDashboard":"panel.Dashboard",
|
"routePanelDashboard": "panel.Dashboard",
|
||||||
}
|
}
|
||||||
err := renameRoutes(routes)
|
err := renameRoutes(routes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = execStmt(qgen.Builder.DropTable("updates"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = execStmt(qgen.Builder.CreateTable("updates", "", "",
|
err = execStmt(qgen.Builder.CreateTable("updates", "", "",
|
||||||
[]qgen.DBTableColumn{
|
[]qgen.DBTableColumn{
|
||||||
qgen.DBTableColumn{"dbVersion", "int", 0, false, false, "0"},
|
qgen.DBTableColumn{"dbVersion", "int", 0, false, false, "0"},
|
||||||
|
@ -376,3 +381,29 @@ func patch8(scanner *bufio.Scanner) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func patch9(scanner *bufio.Scanner) error {
|
||||||
|
// Table "updates" might not exist due to the installer, so drop it and remake it if so
|
||||||
|
err := patch8(scanner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = execStmt(qgen.Builder.CreateTable("login_logs", "", "",
|
||||||
|
[]qgen.DBTableColumn{
|
||||||
|
qgen.DBTableColumn{"lid", "int", 0, false, true, ""},
|
||||||
|
qgen.DBTableColumn{"uid", "int", 0, false, false, ""},
|
||||||
|
qgen.DBTableColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
|
||||||
|
qgen.DBTableColumn{"ipaddress", "varchar", 200, false, false, ""},
|
||||||
|
qgen.DBTableColumn{"doneAt", "createdAt", 0, false, false, ""},
|
||||||
|
},
|
||||||
|
[]qgen.DBTableKey{
|
||||||
|
qgen.DBTableKey{"lid", "primary"},
|
||||||
|
},
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,8 @@ func userRoutes() *RouteGroup {
|
||||||
MemberView("routes.AccountEditEmail", "/user/edit/email/"),
|
MemberView("routes.AccountEditEmail", "/user/edit/email/"),
|
||||||
Action("routes.AccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"),
|
Action("routes.AccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"),
|
||||||
|
|
||||||
|
MemberView("routes.AccountLogins", "/user/edit/logins/"),
|
||||||
|
|
||||||
MemberView("routes.LevelList", "/user/levels/"),
|
MemberView("routes.LevelList", "/user/levels/"),
|
||||||
//MemberView("routes.LevelRankings", "/user/rankings/"),
|
//MemberView("routes.LevelRankings", "/user/rankings/"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,14 +42,31 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User
|
||||||
username := common.SanitiseSingleLine(r.PostFormValue("username"))
|
username := common.SanitiseSingleLine(r.PostFormValue("username"))
|
||||||
uid, err, requiresExtraAuth := common.Auth.Authenticate(username, r.PostFormValue("password"))
|
uid, err, requiresExtraAuth := common.Auth.Authenticate(username, r.PostFormValue("password"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
{
|
||||||
|
// TODO: uid is currently set to 0 as authenticate fetches the user by username and password. Get the actual uid, so we can alert the user of attempted logins? What if someone takes advantage of the response times to deduce if an account exists?
|
||||||
|
logItem := &common.LoginLogItem{UID: uid, Success: false, IPAddress: user.LastIP}
|
||||||
|
_, err := logItem.Create()
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
return common.LocalError(err.Error(), w, r, user)
|
return common.LocalError(err.Error(), w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Take 2FA into account
|
||||||
|
logItem := &common.LoginLogItem{UID: uid, Success: true, IPAddress: user.LastIP}
|
||||||
|
_, err = logItem.Create()
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Do we want to slacken this by only doing it when the IP changes?
|
// TODO: Do we want to slacken this by only doing it when the IP changes?
|
||||||
if requiresExtraAuth {
|
if requiresExtraAuth {
|
||||||
provSession, signedSession, err := common.Auth.CreateProvisionalSession(uid)
|
provSession, signedSession, err := common.Auth.CreateProvisionalSession(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.InternalError(err, w, r)
|
return common.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
// TODO: Use the login log ID in the provisional cookie?
|
||||||
common.Auth.SetProvisionalCookies(w, uid, provSession, signedSession)
|
common.Auth.SetProvisionalCookies(w, uid, provSession, signedSession)
|
||||||
http.Redirect(w, r, "/accounts/mfa_verify/", http.StatusSeeOther)
|
http.Redirect(w, r, "/accounts/mfa_verify/", http.StatusSeeOther)
|
||||||
return nil
|
return nil
|
||||||
|
@ -365,7 +382,7 @@ func AccountEdit(w http.ResponseWriter, r *http.Request, user common.User, heade
|
||||||
nextScore := common.GetLevelScore(user.Level+1) - prevScore
|
nextScore := common.GetLevelScore(user.Level+1) - prevScore
|
||||||
perc := int(math.Ceil((float64(nextScore) / float64(currentScore)) * 100))
|
perc := int(math.Ceil((float64(nextScore) / float64(currentScore)) * 100))
|
||||||
|
|
||||||
pi := common.AccountDashPage{header, "dashboard", "account_own_edit", mfaSetup, currentScore, nextScore, user.Level + 1, perc * 2}
|
pi := common.Account{header, "dashboard", "account_own_edit", common.AccountDashPage{header, mfaSetup, currentScore, nextScore, user.Level + 1, perc * 2}}
|
||||||
return renderTemplate("account", w, r, header, pi)
|
return renderTemplate("account", w, r, header, pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,14 +543,7 @@ func AccountEditMFA(w http.ResponseWriter, r *http.Request, user common.User, he
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.Page{header, tList, mfaItem.Scratch}
|
pi := common.Page{header, tList, mfaItem.Scratch}
|
||||||
if common.RunPreRenderHook("pre_render_account_own_edit_mfa", w, r, &user, &pi) {
|
return renderTemplate("account_own_edit_mfa", w, r, header, pi)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = common.Templates.ExecuteTemplate(w, "account_own_edit_mfa.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
return common.InternalError(err, w, r)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not setup, generate a string, otherwise give an option to disable mfa given the right code
|
// If not setup, generate a string, otherwise give an option to disable mfa given the right code
|
||||||
|
@ -555,14 +565,7 @@ func AccountEditMFASetup(w http.ResponseWriter, r *http.Request, user common.Use
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.Page{header, tList, common.FriendlyGAuthSecret(code)}
|
pi := common.Page{header, tList, common.FriendlyGAuthSecret(code)}
|
||||||
if common.RunPreRenderHook("pre_render_account_own_edit_mfa_setup", w, r, &user, &pi) {
|
return renderTemplate("account_own_edit_mfa_setup", w, r, header, pi)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = common.Templates.ExecuteTemplate(w, "account_own_edit_mfa_setup.html", pi)
|
|
||||||
if err != nil {
|
|
||||||
return common.InternalError(err, w, r)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form should bounce the random mfa secret back and the otp to be verified server-side to reduce the chances of a bug arising on the JS side which makes every code mismatch
|
// Form should bounce the random mfa secret back and the otp to be verified server-side to reduce the chances of a bug arising on the JS side which makes every code mismatch
|
||||||
|
@ -650,8 +653,8 @@ func AccountEditEmail(w http.ResponseWriter, r *http.Request, user common.User,
|
||||||
header.AddNotice("account_mail_verify_success")
|
header.AddNotice("account_mail_verify_success")
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := common.EmailListPage{header, emails, nil}
|
pi := common.Account{header, "edit_emails", "account_own_edit_email", common.EmailListPage{header, emails}}
|
||||||
return renderTemplate("account_own_edit_email", w, r, header, pi)
|
return renderTemplate("account", w, r, header, pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should we make this an AnonAction so someone can do this without being logged in?
|
// TODO: Should we make this an AnonAction so someone can do this without being logged in?
|
||||||
|
@ -699,6 +702,24 @@ func AccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, user co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AccountLogins(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
|
||||||
|
accountEditHead("account_logins", w, r, &user, header)
|
||||||
|
|
||||||
|
logCount := common.LoginLogs.GlobalCount()
|
||||||
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||||
|
perPage := 12
|
||||||
|
offset, page, lastPage := common.PageOffset(logCount, page, perPage)
|
||||||
|
|
||||||
|
logs, err := common.LoginLogs.GetOffset(user.ID, offset, perPage)
|
||||||
|
if err != nil {
|
||||||
|
return common.InternalError(err, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageList := common.Paginate(logCount, perPage, 5)
|
||||||
|
pi := common.Account{header, "logins", "account_logins", common.AccountLoginsPage{header, logs, common.Paginator{pageList, page, lastPage}}}
|
||||||
|
return renderTemplate("account", w, r, header, pi)
|
||||||
|
}
|
||||||
|
|
||||||
func LevelList(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
|
func LevelList(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
|
||||||
header.Title = phrases.GetTitlePhrase("account_level_list")
|
header.Title = phrases.GetTitlePhrase("account_level_list")
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,9 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
|
|
||||||
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
|
||||||
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
|
||||||
|
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
|
||||||
|
_, _, lastPage := common.PageOffset(topicItem.PostCount, 1, common.Config.ItemsPerPage)
|
||||||
|
topicItem.LastPage = lastPage
|
||||||
|
|
||||||
header.Hooks.VhookNoRet("forum_trow_assign", &topicItem, &forum)
|
header.Hooks.VhookNoRet("forum_trow_assign", &topicItem, &forum)
|
||||||
topicList = append(topicList, &topicItem)
|
topicList = append(topicList, &topicItem)
|
||||||
|
|
|
@ -107,7 +107,8 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, header
|
||||||
|
|
||||||
// Calculate the offset
|
// Calculate the offset
|
||||||
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
|
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
|
||||||
tpage := common.TopicPage{header, []common.ReplyUser{}, topic, forum, poll, page, lastPage}
|
pageList := common.Paginate(topic.PostCount, common.Config.ItemsPerPage, 5)
|
||||||
|
tpage := common.TopicPage{header, []common.ReplyUser{}, topic, forum, poll, common.Paginator{pageList, page, lastPage}}
|
||||||
|
|
||||||
// Get the replies if we have any...
|
// Get the replies if we have any...
|
||||||
if topic.PostCount > 0 {
|
if topic.PostCount > 0 {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE [login_logs] (
|
||||||
|
[lid] int not null IDENTITY,
|
||||||
|
[uid] int not null,
|
||||||
|
[success] bool DEFAULT 0 not null,
|
||||||
|
[ipaddress] nvarchar (200) not null,
|
||||||
|
[doneAt] datetime not null,
|
||||||
|
primary key([lid])
|
||||||
|
);
|
|
@ -0,0 +1,3 @@
|
||||||
|
CREATE TABLE [updates] (
|
||||||
|
[dbVersion] int DEFAULT 0 not null
|
||||||
|
);
|
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE `login_logs` (
|
||||||
|
`lid` int not null AUTO_INCREMENT,
|
||||||
|
`uid` int not null,
|
||||||
|
`success` bool DEFAULT 0 not null,
|
||||||
|
`ipaddress` varchar(200) not null,
|
||||||
|
`doneAt` datetime not null,
|
||||||
|
primary key(`lid`)
|
||||||
|
);
|
|
@ -0,0 +1,3 @@
|
||||||
|
CREATE TABLE `updates` (
|
||||||
|
`dbVersion` int DEFAULT 0 not null
|
||||||
|
);
|
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE "login_logs" (
|
||||||
|
`lid` serial not null,
|
||||||
|
`uid` int not null,
|
||||||
|
`success` bool DEFAULT 0 not null,
|
||||||
|
`ipaddress` varchar (200) not null,
|
||||||
|
`doneAt` timestamp not null,
|
||||||
|
primary key(`lid`)
|
||||||
|
);
|
|
@ -0,0 +1,3 @@
|
||||||
|
CREATE TABLE "updates" (
|
||||||
|
`dbVersion` int DEFAULT 0 not null
|
||||||
|
);
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"DBVersion":"9",
|
"DBVersion":"10",
|
||||||
"DynamicFileVersion":"0",
|
"DynamicFileVersion":"0",
|
||||||
"MinGoVersion":"1.11",
|
"MinGoVersion":"1.11",
|
||||||
"MinVersion":""
|
"MinVersion":""
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{template "header.html" . }}
|
{{template "header.html" . }}
|
||||||
<div id="account_{{.HTMLID}}" class="colstack account">
|
<div id="account_{{.HTMLID}}" class="colstack account">
|
||||||
{{template "account_menu.html" . }}
|
{{template "account_menu.html" . }}
|
||||||
<main class="colstack_right">{{dyntmpl .TmplName . .Header}}</main>
|
<main class="colstack_right">{{dyntmpl .TmplName .Inner .Header}}</main>
|
||||||
</div>
|
</div>
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="colstack_item colstack_head rowhead">
|
||||||
|
<div class="rowitem"><h1>{{lang "account_logins_head"}}</h1></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item rowlist">
|
||||||
|
<!-- TODO: Do we need this inline CSS? -->
|
||||||
|
{{range .ItemList}}
|
||||||
|
<div class="rowitem">
|
||||||
|
<span style="float: left;">
|
||||||
|
<span>{{if .Success}}{{lang "account_logins_success"}}{{else}}{{lang "account_logins_failure"}}"{{end}}</span><br />
|
||||||
|
<small style="margin-left: 2px;font-size: 12px;" title="{{.IPAddress}}">{{.IPAddress}}</small>
|
||||||
|
</span>
|
||||||
|
<span class="to_right">
|
||||||
|
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if gt .LastPage 1}}
|
||||||
|
{{template "paginator.html" . }}
|
||||||
|
{{end}}
|
|
@ -8,6 +8,7 @@
|
||||||
<div class="rowitem passive"><a href="/user/edit/password/">{{lang "account_menu_password"}}</a></div>
|
<div class="rowitem passive"><a href="/user/edit/password/">{{lang "account_menu_password"}}</a></div>
|
||||||
<div class="rowitem passive"><a href="/user/edit/email/">{{lang "account_menu_email"}}</a></div>
|
<div class="rowitem passive"><a href="/user/edit/email/">{{lang "account_menu_email"}}</a></div>
|
||||||
<!--<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a> <span class="account_soon">Coming Soon</span></div>-->
|
<!--<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a> <span class="account_soon">Coming Soon</span></div>-->
|
||||||
|
<div class="rowitem passive"><a href="/user/edit/logins/">{{lang "account_menu_logins"}}</a></div>
|
||||||
{{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}}
|
{{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,22 +1,15 @@
|
||||||
{{template "header.html" . }}
|
<div class="colstack_item colstack_head rowhead">
|
||||||
<div id="account_edit_emails" class="colstack account">
|
<div class="rowitem"><h1>{{lang "account_email_head"}}</h1></div>
|
||||||
{{template "account_menu.html" . }}
|
</div>
|
||||||
<main class="colstack_right">
|
<div class="colstack_item rowlist">
|
||||||
<div class="colstack_item colstack_head rowhead">
|
<!-- TODO: Do we need this inline CSS? -->
|
||||||
<div class="rowitem"><h1>{{lang "account_email_head"}}</h1></div>
|
{{range .ItemList}}
|
||||||
</div>
|
<div class="rowitem">
|
||||||
<div class="colstack_item rowlist">
|
<a>{{.Email}}</a>
|
||||||
<!-- TODO: Do we need this inline CSS? -->
|
<span class="to_right">
|
||||||
{{range .ItemList}}
|
<span class="username">{{if .Primary}}{{lang "account_email_primary"}}{{else}}{{lang "account_email_secondary"}}{{end}}</span>
|
||||||
<div class="rowitem" style="font-weight: normal;">
|
{{if .Validated}}<span class="username validated_email">{{lang "account_email_verified"}}</span>{{else}}<a class="username invalid_email">{{lang "account_email_resend_email"}}</a>{{end}}
|
||||||
<a style="text-transform: none;">{{.Email}}</a>
|
</span>
|
||||||
<span style="float: right" class="to_right">
|
</div>
|
||||||
<span class="username">{{if .Primary}}{{lang "account_email_primary"}}{{else}}{{lang "account_email_secondary"}}{{end}}</span>
|
{{end}}
|
||||||
{{if .Validated}}<span class="username" style="color: green;">{{lang "account_email_verified"}}</span>{{else}}<a class="username" style="color: crimson;">{{lang "account_email_resend_email"}}</a>{{end}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
{{template "footer.html" . }}
|
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||||
<span>
|
<span>
|
||||||
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
||||||
<span class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{.RelativeLastReplyAt}}</span>
|
<a href="{{.Link}}?page={{.LastPage}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{.RelativeLastReplyAt}}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<div class="rowitem panel_compactrow">
|
<div class="rowitem panel_compactrow">
|
||||||
<span style="float: left;">
|
<span style="float: left;">
|
||||||
<span>{{.Action}}</span>
|
<span>{{.Action}}</span>
|
||||||
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small>{{end}}
|
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small style="margin-left: 2px;font-size: 12px;" title="{{.IPAddress}}">{{.IPAddress}}</small>{{end}}
|
||||||
</span>
|
</span>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<div class="rowitem panel_compactrow">
|
<div class="rowitem panel_compactrow">
|
||||||
<span style="float: left;">
|
<span style="float: left;">
|
||||||
<span>{{.Action}}</span>
|
<span>{{.Action}}</span>
|
||||||
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small>{{end}}
|
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small style="margin-left: 2px;font-size: 12px;" title="{{.IPAddress}}">{{.IPAddress}}</small>{{end}}
|
||||||
</span>
|
</span>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<div class="rowitem panel_compactrow">
|
<div class="rowitem panel_compactrow">
|
||||||
<span{{if not .Success}} class="panel_registration_attempt"{{end}} style="float: left;">
|
<span{{if not .Success}} class="panel_registration_attempt"{{end}} style="float: left;">
|
||||||
<span>{{if not .Success}}{{lang "panel_logs_registration_attempt"}}: {{end}}{{.Username}} ({{lang "panel_logs_registration_email"}}: {{.Email}}){{if .ParsedReason}} ({{lang "panel_logs_registration_reason"}}: {{.ParsedReason}}){{end}}</span>
|
<span>{{if not .Success}}{{lang "panel_logs_registration_attempt"}}: {{end}}{{.Username}} ({{lang "panel_logs_registration_email"}}: {{.Email}}){{if .ParsedReason}} ({{lang "panel_logs_registration_reason"}}: {{.ParsedReason}}){{end}}</span>
|
||||||
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small>{{end}}
|
{{if $.CurrentUser.Perms.ViewIPs}}<br /><small style="margin-left: 2px;font-size: 12px;" title="{{.IPAddress}}">{{.IPAddress}}</small>{{end}}
|
||||||
</span>
|
</span>
|
||||||
<span class="to_right">
|
<span class="to_right">
|
||||||
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
<span style="font-size: 14px;" title="{{.DoneAt}}">{{.DoneAt}}</span>
|
||||||
|
|
|
@ -103,6 +103,9 @@
|
||||||
</article>
|
</article>
|
||||||
{{template "topic_alt_posts.html" . }}
|
{{template "topic_alt_posts.html" . }}
|
||||||
</div>
|
</div>
|
||||||
|
{{if gt .LastPage 1}}
|
||||||
|
{{template "paginator.html" . }}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if .CurrentUser.Loggedin}}
|
{{if .CurrentUser.Loggedin}}
|
||||||
{{if .CurrentUser.Perms.CreateReply}}
|
{{if .CurrentUser.Perms.CreateReply}}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" title="{{.LastUser.Name}}'s Avatar" /></a>
|
||||||
<span>
|
<span>
|
||||||
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;" title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
||||||
<span class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{.RelativeLastReplyAt}}</span>
|
<a href="{{.Link}}?page={{.LastPage}}" class="rowsmall lastReplyAt" title="{{abstime .LastReplyAt}}">{{.RelativeLastReplyAt}}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -77,3 +77,10 @@
|
||||||
#dash_right .rowitem:not(:last-child) {
|
#dash_right .rowitem:not(:last-child) {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.validated_email {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.invalid_email {
|
||||||
|
color: crimson;
|
||||||
|
}
|
|
@ -1129,6 +1129,10 @@ textarea {
|
||||||
content: "{{lang "topic.report_button_text" .}}";
|
content: "{{lang "topic.report_button_text" .}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zone_view_topic .pageset {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
#ip_search_container .rowlist .rowitem {
|
#ip_search_container .rowlist .rowitem {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
|
@ -39,6 +39,10 @@
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.to_right {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 420px) {
|
@media (max-width: 420px) {
|
||||||
.colstack {
|
.colstack {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -98,3 +98,13 @@
|
||||||
#dash_right .rowitem:not(:last-child) {
|
#dash_right .rowitem:not(:last-child) {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rowlist .rowitem {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.validated_email {
|
||||||
|
color: rgb(0, 170, 0);
|
||||||
|
}
|
||||||
|
.invalid_email {
|
||||||
|
color: crimson;
|
||||||
|
}
|
|
@ -788,6 +788,9 @@ input[type=checkbox]:checked + label .sel {
|
||||||
content: "{{lang "topic.like_count_suffix" . }}";
|
content: "{{lang "topic.like_count_suffix" . }}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zone_view_topic .pageset {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
.topic_reply_container {
|
.topic_reply_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
background-color: rgb(88,68,68);
|
background-color: rgb(88,68,68);
|
||||||
}
|
}
|
||||||
|
|
||||||
.to_right, .panel_buttons, .panel_floater {
|
.panel_buttons, .panel_floater {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,3 +46,10 @@
|
||||||
.rowmenu .account_soon, .rowmenu .dash_security {
|
.rowmenu .account_soon, .rowmenu .dash_security {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.validated_email {
|
||||||
|
color: rgb(0, 170, 0);
|
||||||
|
}
|
||||||
|
.invalid_email {
|
||||||
|
color: crimson;
|
||||||
|
}
|
|
@ -49,3 +49,10 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: maroon;
|
color: maroon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.validated_email {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.invalid_email {
|
||||||
|
color: crimson;
|
||||||
|
}
|
Loading…
Reference in New Issue