From fa065bc58415ccaa2b157fe746b10f0ec1bc53cf Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 10 Feb 2018 15:07:21 +0000 Subject: [PATCH] /topics/ (the default index) is now over 25 times faster than before. Change the length of the ticks for referrer tracking. Fixed a bug in Shadow and Tempra Simple where the polls would appear when they shouldn't. Added styling to Shadow for Control Panel Sub-menus. Fixed the ridiculously wide attachment images on Cosora. The analytics pages should no longer be treated as the dashboard style-wise. The installer now works again. Tests now run again, as do the benchmarks. Refactored the topic list logic, and moved the route portion into the routes package. Fixed an inverted comparison in the referrer logic. Added friendly text for Mac operating systems to the english language pack. Removed two obsolete prepared statements. Fixed three race conditions in the database adapters. Potentially sped up the topic page by 20% Added several new benchmarks and refactored the others. Removed a redundant prepared statement in user.go Added reviseID as a primary key to the revisions table. The user_count column was added to the users_groups table, but it's unknown if it will stay. --- CONTRIBUTING.md | 2 - common/errors.go | 13 +- common/group.go | 1 + common/group_store.go | 9 +- common/requests.go | 4 +- common/topic_list.go | 296 +++++++++++++- common/user.go | 3 +- common/user_store.go | 2 +- config.go | 2 +- gen_mssql.go | 16 - gen_mysql.go | 14 - gen_router.go | 17 +- gen_tables.go | 11 +- general_test.go | 384 +++++++++++++++--- install/install.go | 2 +- langs/english.json | 1 + main.go | 4 + query_gen/lib/mssql.go | 3 + query_gen/lib/mysql.go | 3 + query_gen/lib/pgsql.go | 3 + query_gen/main.go | 6 +- query_gen/tables.go | 5 +- router_gen/main.go | 5 + router_gen/routes.go | 2 +- routes.go | 135 ------ routes/topic.go | 29 +- routes/topic_list.go | 60 +++ schema/mssql/query_revisions.sql | 3 +- schema/mssql/query_users_groups.sql | 1 + schema/mysql/query_revisions.sql | 3 +- schema/mysql/query_users_groups.sql | 1 + schema/pgsql/query_revisions.sql | 3 +- schema/pgsql/query_users_groups.sql | 1 + template_list.go | 216 +++++----- template_topic.go | 250 ++++++------ templates/panel_analytics_agent_views.html | 2 +- templates/panel_analytics_agents.html | 2 +- templates/panel_analytics_posts.html | 2 +- templates/panel_analytics_referrer_views.html | 2 +- templates/panel_analytics_referrers.html | 2 +- templates/panel_analytics_route_views.html | 2 +- templates/panel_analytics_routes.html | 2 +- templates/panel_analytics_system_views.html | 2 +- templates/panel_analytics_systems.html | 2 +- templates/panel_analytics_topics.html | 2 +- templates/panel_analytics_views.html | 2 +- templates/topic.html | 2 + themes/cosora/public/main.css | 3 + themes/shadow/public/panel.css | 13 + 49 files changed, 1038 insertions(+), 512 deletions(-) create mode 100644 routes/topic_list.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d847602e..557b2584 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,8 +24,6 @@ Always use strict mode. Don't worry about ES5, we're targetting modern browsers. If we decide to backport code to older browsers, then we'll transpile the files. -Don't use await until Edge gets around to implementing it. - To keep consistency with Go code, variables must be camelCase. # JSON diff --git a/common/errors.go b/common/errors.go index 79f39ce6..88896abb 100644 --- a/common/errors.go +++ b/common/errors.go @@ -1,9 +1,11 @@ package common -import "log" -import "sync" -import "net/http" -import "runtime/debug" +import ( + "log" + "net/http" + "runtime/debug" + "sync" +) // TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? // ? - Should we pass HeaderVars / HeaderLite rather than forcing the errors to pull the global HeaderVars instance? @@ -120,6 +122,7 @@ func SilentInternalErrorXML(err error, w http.ResponseWriter, r *http.Request) R } func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { + //LogError(errors.New(errmsg)) w.WriteHeader(500) pi := Page{"Error", GuestUser, DefaultHeaderVar(), tList, errmsg} handleErrorTemplate(w, r, pi) @@ -141,6 +144,7 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo // LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError { + //LogError(errors.New(errmsg)) w.WriteHeader(500) pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg} handleErrorTemplate(w, r, pi) @@ -277,6 +281,7 @@ func CustomErrorJS(errmsg string, errcode int, w http.ResponseWriter, r *http.Re } func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) { + //LogError(errors.New("error happened")) // TODO: What to do about this hook? if PreRenderHooks["pre_render_error"] != nil { if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) { diff --git a/common/group.go b/common/group.go index cee41f91..7adacba0 100644 --- a/common/group.go +++ b/common/group.go @@ -28,6 +28,7 @@ type Group struct { PluginPermsText []byte Forums []*ForumPerms CanSee []int // The IDs of the forums this group can see + UserCount int // ! Might be temporary as I might want to lean on the database instead for this } type GroupStmts struct { diff --git a/common/group_store.go b/common/group_store.go index d1efcab4..8a0b1761 100644 --- a/common/group_store.go +++ b/common/group_store.go @@ -39,6 +39,7 @@ type MemoryGroupStore struct { getAll *sql.Stmt get *sql.Stmt count *sql.Stmt + userCount *sql.Stmt sync.RWMutex } @@ -51,6 +52,7 @@ func NewMemoryGroupStore() (*MemoryGroupStore, error) { getAll: acc.Select("users_groups").Columns("gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag").Prepare(), get: acc.Select("users_groups").Columns("name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag").Where("gid = ?").Prepare(), count: acc.Count("users_groups").Prepare(), + userCount: acc.Count("users").Where("group = ?").Prepare(), }, acc.FirstError() } @@ -177,6 +179,11 @@ func (mgs *MemoryGroupStore) initGroup(group *Group) error { if group.IsAdmin || group.IsMod { group.IsBanned = false } + + err = mgs.userCount.QueryRow(group.ID).Scan(&group.UserCount) + if err != sql.ErrNoRows { + return err + } return nil } @@ -274,7 +281,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod } mgs.Lock() - mgs.groups[gid] = &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList} + mgs.groups[gid] = &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList, 0} mgs.groupCount++ mgs.Unlock() diff --git a/common/requests.go b/common/requests.go index 10678a21..52ac3bbe 100644 --- a/common/requests.go +++ b/common/requests.go @@ -35,8 +35,8 @@ func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) { even: make(map[string]*ReferrerItem), insert: acc.Insert("viewchunks_referrers").Columns("count, createdAt, domain").Fields("?,UTC_TIMESTAMP(),?").Prepare(), // TODO: Do something more efficient than doing a query for each referrer } - //AddScheduledFifteenMinuteTask(refTracker.Tick) - AddScheduledSecondTask(refTracker.Tick) + AddScheduledFifteenMinuteTask(refTracker.Tick) + //AddScheduledSecondTask(refTracker.Tick) AddShutdownTask(refTracker.Tick) return refTracker, acc.FirstError() } diff --git a/common/topic_list.go b/common/topic_list.go index b6282417..f5eaac5e 100644 --- a/common/topic_list.go +++ b/common/topic_list.go @@ -1,7 +1,299 @@ package common -import "strconv" -import "../query_gen/lib" +import ( + "strconv" + "sync" + + "../query_gen/lib" +) + +var TopicList TopicListInt + +type TopicListHolder struct { + List []*TopicsRow + ForumList []Forum + PageList []int + Page int + LastPage int +} + +type TopicListInt interface { + GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) + GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) +} + +type DefaultTopicList struct { + oddGroups map[int]*TopicListHolder + evenGroups map[int]*TopicListHolder + oddLock sync.RWMutex + evenLock sync.RWMutex + + groupList []int // TODO: Use an atomic.Value instead to allow this to be updated on long ticks +} + +func NewDefaultTopicList() (*DefaultTopicList, error) { + tList := &DefaultTopicList{ + oddGroups: make(map[int]*TopicListHolder), + evenGroups: make(map[int]*TopicListHolder), + } + + var slots = make([]int, 8) // Only cache the topic list for eight groups + + // TODO: Do something more efficient than this + allGroups, err := Groups.GetAll() + if err != nil { + return nil, err + } + + if len(allGroups) > 0 { + var stopHere int + if len(allGroups) <= 8 { + stopHere = len(allGroups) + } else { + stopHere = 8 + } + + var lowest = allGroups[0].UserCount + for i := 0; i < stopHere; i++ { + slots[i] = i + if allGroups[i].UserCount < lowest { + lowest = allGroups[i].UserCount + } + } + + var findNewLowest = func() { + for _, slot := range slots { + if allGroups[slot].UserCount < lowest { + lowest = allGroups[slot].UserCount + } + } + } + + for i := 8; i < len(allGroups); i++ { + if allGroups[i].UserCount > lowest { + for ii, slot := range slots { + if allGroups[i].UserCount > slot { + slots[ii] = i + lowest = allGroups[i].UserCount + findNewLowest() + break + } + } + } + } + + tList.groupList = slots + } + + err = tList.Tick() + if err != nil { + return nil, err + } + AddScheduledHalfSecondTask(tList.Tick) + //AddScheduledSecondTask(tList.GroupCountTick) // TODO: Dynamically change the groups in the short list to be optimised every second + return tList, nil +} + +// TODO: Add support for groups other than the guest group +func (tList *DefaultTopicList) Tick() error { + var oddLists = make(map[int]*TopicListHolder) + var evenLists = make(map[int]*TopicListHolder) + + var addList = func(gid int, topicList []*TopicsRow, forumList []Forum, pageList []int, page int, lastPage int) { + if gid%2 == 0 { + evenLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage} + } else { + oddLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage} + } + } + + guestGroup, err := Groups.Get(GuestUser.Group) + if err != nil { + return err + } + topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(guestGroup, 1) + if err != nil { + return err + } + addList(guestGroup.ID, topicList, forumList, pageList, page, lastPage) + + for _, gid := range tList.groupList { + group, err := Groups.Get(gid) // TODO: Bulk load the groups? + if err != nil { + return err + } + topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(group, 1) + if err != nil { + return err + } + addList(group.ID, topicList, forumList, pageList, page, lastPage) + } + + tList.oddLock.Lock() + tList.oddGroups = oddLists + tList.oddLock.Unlock() + + tList.evenLock.Lock() + tList.evenGroups = evenLists + tList.evenLock.Unlock() + + return nil +} + +func (tList *DefaultTopicList) GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) { + // TODO: Cache the first three pages not just the first along with all the topics on this beaten track + if page == 1 { + var holder *TopicListHolder + var ok bool + if group.ID%2 == 0 { + tList.evenLock.RLock() + holder, ok = tList.evenGroups[group.ID] + tList.evenLock.RUnlock() + } else { + tList.oddLock.RLock() + holder, ok = tList.oddGroups[group.ID] + tList.oddLock.RUnlock() + } + if ok { + return holder.List, holder.ForumList, holder.PageList, holder.Page, holder.LastPage, nil + } + } + + return tList.getListByGroup(group, page) +} + +func (tList *DefaultTopicList) getListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) { + // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? + canSee := group.CanSee + + // We need a list of the visible forums for Quick Topic + // ? - Would it be useful, if we could post in social groups from /topics/? + for _, fid := range canSee { + forum := Forums.DirtyGet(fid) + if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") { + fcopy := forum.Copy() + // TODO: Add a hook here for plugin_guilds + forumList = append(forumList, fcopy) + } + } + + // ? - Should we be showing plugin_guilds posts on /topics/? + argList, qlist := ForumListToArgQ(forumList) + if qlist == "" { + // We don't want to kill the page, so pass an empty slice and nil error + return topicList, forumList, pageList, 1, 1, nil + } + + topicList, pageList, page, lastPage, err = tList.getList(page, argList, qlist) + return topicList, forumList, pageList, page, lastPage, err +} + +// TODO: Reduce the number of returns +func (tList *DefaultTopicList) GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) { + // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? + canSee, err := Forums.GetAllVisibleIDs() + if err != nil { + return nil, nil, nil, 1, 1, err + } + + // We need a list of the visible forums for Quick Topic + // ? - Would it be useful, if we could post in social groups from /topics/? + for _, fid := range canSee { + forum := Forums.DirtyGet(fid) + if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") { + fcopy := forum.Copy() + // TODO: Add a hook here for plugin_guilds + forumList = append(forumList, fcopy) + } + } + + // ? - Should we be showing plugin_guilds posts on /topics/? + argList, qlist := ForumListToArgQ(forumList) + if qlist == "" { + // If the super admin can't see anything, then things have gone terribly wrong + return topicList, forumList, pageList, 1, 1, err + } + + topicList, pageList, outPage, lastPage, err = tList.getList(page, argList, qlist) + return topicList, forumList, pageList, outPage, lastPage, err +} + +// TODO: Rename this to TopicListStore and pass back a TopicList instance holding the pagination data and topic list rather than passing them back one argument at a time +func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist string) (topicList []*TopicsRow, pageList []int, outPage int, lastPage int, err error) { + topicCount, err := ArgQToTopicCount(argList, qlist) + if err != nil { + return nil, nil, 1, 1, err + } + offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage) + + stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?") + if err != nil { + return nil, nil, 1, 1, err + } + defer stmt.Close() + + argList = append(argList, offset) + argList = append(argList, Config.ItemsPerPage) + + rows, err := stmt.Query(argList...) + if err != nil { + return nil, nil, 1, 1, err + } + defer rows.Close() + + var reqUserList = make(map[int]bool) + for rows.Next() { + topicItem := TopicsRow{ID: 0} + err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount) + if err != nil { + return nil, nil, 1, 1, err + } + + topicItem.Link = BuildTopicURL(NameToSlug(topicItem.Title), topicItem.ID) + forum := Forums.DirtyGet(topicItem.ParentID) + topicItem.ForumName = forum.Name + topicItem.ForumLink = forum.Link + + //topicItem.CreatedAt = RelativeTime(topicItem.CreatedAt) + topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt) + + // TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/ + if Vhooks["topics_topic_row_assign"] != nil { + RunVhook("topics_topic_row_assign", &topicItem, &forum) + } + topicList = append(topicList, &topicItem) + reqUserList[topicItem.CreatedBy] = true + reqUserList[topicItem.LastReplyBy] = true + } + err = rows.Err() + if err != nil { + return nil, nil, 1, 1, err + } + + // Convert the user ID map to a slice, then bulk load the users + var idSlice = make([]int, len(reqUserList)) + var i int + for userID := range reqUserList { + idSlice[i] = userID + i++ + } + + // TODO: What if a user is deleted via the Control Panel? + userList, err := Users.BulkGetMap(idSlice) + if err != nil { + return nil, nil, 1, 1, err + } + + // Second pass to the add the user data + // TODO: Use a pointer to TopicsRow instead of TopicsRow itself? + for _, topicItem := range topicList { + topicItem.Creator = userList[topicItem.CreatedBy] + topicItem.LastUser = userList[topicItem.LastReplyBy] + } + + pageList = Paginate(topicCount, Config.ItemsPerPage, 5) + return topicList, pageList, page, lastPage, nil +} // Internal. Don't rely on it. func ForumListToArgQ(forums []Forum) (argList []interface{}, qlist string) { diff --git a/common/user.go b/common/user.go index 71285bd9..0216930d 100644 --- a/common/user.go +++ b/common/user.go @@ -82,11 +82,10 @@ func init() { DbInits.Add(func(acc *qgen.Accumulator) error { userStmts = UserStmts{ activate: acc.SimpleUpdate("users", "active = 1", "uid = ?"), - changeGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"), + changeGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"), // TODO: Implement user_count for users_groups here delete: acc.SimpleDelete("users", "uid = ?"), setAvatar: acc.SimpleUpdate("users", "avatar = ?", "uid = ?"), setUsername: acc.SimpleUpdate("users", "name = ?", "uid = ?"), - updateGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"), incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", "uid = ?"), updateLevel: acc.SimpleUpdate("users", "level = ?", "uid = ?"), incrementScore: acc.SimpleUpdate("users", "score = score + ?", "uid = ?"), diff --git a/common/user_store.go b/common/user_store.go index 513d3899..494a461e 100644 --- a/common/user_store.go +++ b/common/user_store.go @@ -51,7 +51,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) { cache: cache, get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), - register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), + register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""), userCount: acc.SimpleCount("users", "", ""), }, acc.FirstError() diff --git a/config.go b/config.go index 5ced30ea..5bb60486 100644 --- a/config.go +++ b/config.go @@ -45,7 +45,7 @@ func init() { common.Config.SMTPPort = "25" // Misc - common.Config.DefaultRoute = "routeTopics" + common.Config.DefaultRoute = "routes.TopicList" common.Config.DefaultGroup = 3 // Should be a setting in the database common.Config.ActivationGroup = 5 // Should be a setting in the database common.Config.StaffCSS = "staff_post" diff --git a/gen_mssql.go b/gen_mssql.go index 279c1925..805df442 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -25,8 +25,6 @@ type Stmts struct { groupEntryExists *sql.Stmt getForumTopicsOffset *sql.Stmt getAttachment *sql.Stmt - getTopicList *sql.Stmt - getTopicReplies *sql.Stmt getForumTopics *sql.Stmt getProfileReplies *sql.Stmt getWatchers *sql.Stmt @@ -182,20 +180,6 @@ func _gen_mssql() (err error) { return err } - log.Print("Preparing getTopicList statement.") - stmts.getTopicList, err = db.Prepare("SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") - if err != nil { - log.Print("Bad Query: ","SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") - return err - } - - log.Print("Preparing getTopicReplies statement.") - stmts.getTopicReplies, err = db.Prepare("SELECT [replies].[rid],[replies].[content],[replies].[createdBy],[replies].[createdAt],[replies].[lastEdit],[replies].[lastEditBy],[users].[avatar],[users].[name],[users].[group],[users].[url_prefix],[users].[url_name],[users].[level],[replies].[ipaddress] FROM [replies] LEFT JOIN [users] ON [replies].[createdBy] = [users].[uid] WHERE [tid] = ?1") - if err != nil { - log.Print("Bad Query: ","SELECT [replies].[rid],[replies].[content],[replies].[createdBy],[replies].[createdAt],[replies].[lastEdit],[replies].[lastEditBy],[users].[avatar],[users].[name],[users].[group],[users].[url_prefix],[users].[url_name],[users].[level],[replies].[ipaddress] FROM [replies] LEFT JOIN [users] ON [replies].[createdBy] = [users].[uid] WHERE [tid] = ?1") - return err - } - log.Print("Preparing getForumTopics statement.") stmts.getForumTopics, err = db.Prepare("SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[lastReplyAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] WHERE [topics].[parentID] = ?1 ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") if err != nil { diff --git a/gen_mysql.go b/gen_mysql.go index 6c4c5ce8..2d2ed881 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -27,8 +27,6 @@ type Stmts struct { groupEntryExists *sql.Stmt getForumTopicsOffset *sql.Stmt getAttachment *sql.Stmt - getTopicList *sql.Stmt - getTopicReplies *sql.Stmt getForumTopics *sql.Stmt getProfileReplies *sql.Stmt getWatchers *sql.Stmt @@ -168,18 +166,6 @@ func _gen_mysql() (err error) { return err } - log.Print("Preparing getTopicList statement.") - stmts.getTopicList, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") - if err != nil { - return err - } - - log.Print("Preparing getTopicReplies statement.") - stmts.getTopicReplies, err = db.Prepare("SELECT `replies`.`rid`, `replies`.`content`, `replies`.`createdBy`, `replies`.`createdAt`, `replies`.`lastEdit`, `replies`.`lastEditBy`, `users`.`avatar`, `users`.`name`, `users`.`group`, `users`.`url_prefix`, `users`.`url_name`, `users`.`level`, `replies`.`ipaddress` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ?") - if err != nil { - return err - } - log.Print("Preparing getForumTopics statement.") stmts.getForumTopics, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`lastReplyAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") if err != nil { diff --git a/gen_router.go b/gen_router.go index 74f09371..03dab123 100644 --- a/gen_router.go +++ b/gen_router.go @@ -26,7 +26,7 @@ var RouteMap = map[string]interface{}{ "routeWebsockets": routeWebsockets, "routeReportSubmit": routeReportSubmit, "routes.CreateTopic": routes.CreateTopic, - "routeTopics": routeTopics, + "routes.TopicList": routes.TopicList, "routePanelForums": routePanelForums, "routePanelForumsCreateSubmit": routePanelForumsCreateSubmit, "routePanelForumsDelete": routePanelForumsDelete, @@ -128,7 +128,7 @@ var routeMapEnum = map[string]int{ "routeWebsockets": 7, "routeReportSubmit": 8, "routes.CreateTopic": 9, - "routeTopics": 10, + "routes.TopicList": 10, "routePanelForums": 11, "routePanelForumsCreateSubmit": 12, "routePanelForumsDelete": 13, @@ -228,7 +228,7 @@ var reverseRouteMapEnum = map[int]string{ 7: "routeWebsockets", 8: "routeReportSubmit", 9: "routes.CreateTopic", - 10: "routeTopics", + 10: "routes.TopicList", 11: "routePanelForums", 12: "routePanelForumsCreateSubmit", 13: "routePanelForumsDelete", @@ -524,6 +524,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Deflect malformed requests if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { + //log.Print("len(req.URL.Path): ",len(req.URL.Path)) + //log.Print("req.URL.Path[0]: ",req.URL.Path[0]) + //log.Print("req.Host: ",req.Host) + //log.Print("common.Site.Host: ",common.Site.Host) + w.WriteHeader(200) // 400 w.Write([]byte("")) log.Print("Malformed Request") @@ -687,13 +692,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.OSViewCounter.Bump(osMapEnum[os]) } - referrer := req.Header.Get("Referer") // Check the referrer header too? :P + referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P if referrer != "" { // ? Optimise this a little? referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://") referrer = strings.Split(referrer,"/")[0] portless := strings.Split(referrer,":")[0] - if portless != "localhost" && portless != "127.0.0.1" && portless == common.Site.Host { + if portless != "localhost" && portless != "127.0.0.1" && portless != common.Site.Host { common.ReferrerTracker.Bump(referrer) } } @@ -811,7 +816,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { err = routes.CreateTopic(w,req,user,extraData) default: common.RouteViewCounter.Bump(10) - err = routeTopics(w,req,user) + err = routes.TopicList(w,req,user) } if err != nil { router.handleError(err,w,req,user) diff --git a/gen_tables.go b/gen_tables.go index dea7c928..304e49ad 100644 --- a/gen_tables.go +++ b/gen_tables.go @@ -2,15 +2,16 @@ package main var dbTablePrimaryKeys = map[string]string{ - "users_groups":"gid", - "users_groups_scheduler":"uid", "replies":"rid", - "word_filters":"wfid", + "revisions":"reviseID", "users_replies":"rid", "activity_stream":"asid", - "users":"uid", - "forums":"fid", + "word_filters":"wfid", + "users_groups":"gid", "topics":"tid", + "forums":"fid", "attachments":"attachID", "polls":"pollID", + "users":"uid", + "users_groups_scheduler":"uid", } diff --git a/general_test.go b/general_test.go index 05ef7c95..98a7f3f5 100644 --- a/general_test.go +++ b/general_test.go @@ -15,6 +15,7 @@ import ( "./common" "./install/install" "./query_gen/lib" + "./routes" //"github.com/husobee/vestigo" ) @@ -99,35 +100,102 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) { b.Fatal(err) } } + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + 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") + } + adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year} + adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year} b.RunParallel(func(pb *testing.PB) { - admin, err := common.Users.Get(1) + for pb.Next() { + topicW := httptest.NewRecorder() + topicReqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + topicReqAdmin.AddCookie(&adminUIDCookie) + topicReqAdmin.AddCookie(&adminSessionCookie) + + // Deal with the session stuff, etc. + user, ok := common.PreRoute(topicW, topicReqAdmin) + if !ok { + b.Fatal("Mysterious error!") + } + //topicW.Body.Reset() + routes.ViewTopic(topicW, topicReqAdmin, user, "1") + if topicW.Code != 200 { + b.Log(topicW.Body) + b.Fatal("HTTP Error!") + } + } + }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() if err != nil { b.Fatal(err) } - if !admin.IsAdmin { - b.Fatal("UID1 is not an admin") - } - adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year} - adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year} + } + router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false - topicW := httptest.NewRecorder() - topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil)) - topicReqAdmin := topicReq - topicReqAdmin.AddCookie(&adminUIDCookie) - topicReqAdmin.AddCookie(&adminSessionCookie) - - // Deal with the session stuff, etc. - user, ok := common.PreRoute(topicW, topicReqAdmin) - if !ok { - b.Fatal("Mysterious error!") - } + admin, err := common.Users.Get(1) + if err != nil { + b.Fatal(err) + } + if !admin.IsAdmin { + b.Fatal("UID1 is not an admin") + } + adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year} + adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year} + b.RunParallel(func(pb *testing.PB) { for pb.Next() { - topicW.Body.Reset() - routeTopicID(topicW, topicReqAdmin, user) + topicW := httptest.NewRecorder() + topicReqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + topicReqAdmin.AddCookie(&adminUIDCookie) + topicReqAdmin.AddCookie(&adminSessionCookie) + topicReqAdmin.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") + topicReqAdmin.Header.Set("Host", "localhost") + topicReqAdmin.Host = "localhost" + //topicW.Body.Reset() + router.ServeHTTP(topicW, topicReqAdmin) + if topicW.Code != 200 { + b.Log(topicW.Body) + b.Fatal("HTTP Error!") + } } }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkTopicAdminRouteParallelAlt(b *testing.B) { + BenchmarkTopicAdminRouteParallel(b) +} + +func BenchmarkTopicAdminRouteParallelWithRouterAlt(b *testing.B) { + BenchmarkTopicAdminRouteParallelWithRouter(b) +} + +func BenchmarkTopicAdminRouteParallelAltAlt(b *testing.B) { + BenchmarkTopicAdminRouteParallel(b) } func BenchmarkTopicGuestRouteParallel(b *testing.B) { @@ -138,15 +206,225 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) { b.Fatal(err) } } + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false b.RunParallel(func(pb *testing.PB) { - topicW := httptest.NewRecorder() - topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil)) for pb.Next() { - topicW.Body.Reset() - routeTopicID(topicW, topicReq, common.GuestUser) + topicW := httptest.NewRecorder() + topicReq := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + //topicW.Body.Reset() + routes.ViewTopic(topicW, topicReq, common.GuestUser, "1") + if topicW.Code != 200 { + b.Log(topicW.Body) + b.Fatal("HTTP Error!") + } } }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() + if err != nil { + b.Fatal(err) + } + } + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = true + common.Dev.SuperDebug = false + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + topicW := httptest.NewRecorder() + topicReq := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil)) + //topicW.Body.Reset() + routes.ViewTopic(topicW, topicReq, common.GuestUser, "1") + if topicW.Code != 200 { + b.Log(topicW.Body) + b.Fatal("HTTP Error!") + } + } + }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() + if err != nil { + b.Fatal(err) + } + } + router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false + + /*f, err := os.Create("BenchmarkTopicGuestRouteParallelWithRouter.prof") + if err != nil { + b.Fatal(err) + } + pprof.StartCPUProfile(f)*/ + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + topicW := httptest.NewRecorder() + topicReq := httptest.NewRequest("GET", "/topic/hm.1", bytes.NewReader(nil)) + topicReq.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") + topicReq.Header.Set("Host", "localhost") + topicReq.Host = "localhost" + //topicW.Body.Reset() + router.ServeHTTP(topicW, topicReq) + if topicW.Code != 200 { + b.Log(topicW.Body) + b.Fatal("HTTP Error!") + } + } + }) + + //defer pprof.StopCPUProfile() + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() + if err != nil { + b.Fatal(err) + } + } + router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + badW := httptest.NewRecorder() + badReq := httptest.NewRequest("GET", "/garble/haa", bytes.NewReader(nil)) + badReq.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") + badReq.Header.Set("Host", "localhost") + badReq.Host = "localhost" + router.ServeHTTP(badW, badReq) + } + }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkTopicsGuestRouteParallelWithRouter(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() + if err != nil { + b.Fatal(err) + } + } + router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + listW := httptest.NewRecorder() + listReq := httptest.NewRequest("GET", "/topics/", 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") + listReq.Header.Set("Host", "localhost") + listReq.Host = "localhost" + router.ServeHTTP(listW, listReq) + if listW.Code != 200 { + b.Log(listW.Body) + b.Fatal("HTTP Error!") + } + } + }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkForumsGuestRouteParallelWithRouter(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() + if err != nil { + b.Fatal(err) + } + } + router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + listW := httptest.NewRecorder() + listReq := httptest.NewRequest("GET", "/forums/", 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") + listReq.Header.Set("Host", "localhost") + listReq.Host = "localhost" + router.ServeHTTP(listW, listReq) + if listW.Code != 200 { + b.Log(listW.Body) + b.Fatal("HTTP Error!") + } + } + }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 +} + +func BenchmarkForumGuestRouteParallelWithRouter(b *testing.B) { + b.ReportAllocs() + if !gloinited { + err := gloinit() + if err != nil { + b.Fatal(err) + } + } + router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) + prev := common.Dev.DebugMode + prev2 := common.Dev.SuperDebug + common.Dev.DebugMode = false + common.Dev.SuperDebug = false + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + listW := httptest.NewRecorder() + listReq := httptest.NewRequest("GET", "/forum/general.2", 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") + listReq.Header.Set("Host", "localhost") + listReq.Host = "localhost" + router.ServeHTTP(listW, listReq) + if listW.Code != 200 { + b.Log(listW.Body) + b.Fatal("HTTP Error!") + } + } + }) + + common.Dev.DebugMode = prev + common.Dev.SuperDebug = prev2 } // TODO: Make these routes compatible with the changes to the router @@ -165,19 +443,19 @@ func BenchmarkForumsAdminRouteParallel(b *testing.B) { if !admin.Is_Admin { panic("UID1 is not an admin") } - admin_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} - admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,Path:"/",MaxAge: year} + adminUidCookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} + adminSessionCookie := http.Cookie{Name:"session",Value: admin.Session,Path:"/",MaxAge: year} - forums_w := httptest.NewRecorder() - forums_req := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) - forums_req_admin := forums_req - forums_req_admin.AddCookie(&admin_uid_cookie) - forums_req_admin.AddCookie(&admin_session_cookie) - forums_handler := http.HandlerFunc(route_forums) + forumsW := httptest.NewRecorder() + forumsReq := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) + forumsReq_admin := forums_req + forumsReq_admin.AddCookie(&adminUidCookie) + forumsReq_admin.AddCookie(&adminSessionCookie) + forumsHandler := http.HandlerFunc(route_forums) for pb.Next() { - forums_w.Body.Reset() - forums_handler.ServeHTTP(forums_w,forums_req_admin) + forumsW.Body.Reset() + forumsHandler.ServeHTTP(forumsW,forumsReqAdmin) } }) } @@ -196,47 +474,29 @@ func BenchmarkForumsAdminRouteParallelProf(b *testing.B) { if !admin.Is_Admin { panic("UID1 is not an admin") } - admin_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} - admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,Path: "/",MaxAge: year} + adminUidCookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} + adminSessionCookie := http.Cookie{Name:"session",Value: admin.Session,Path: "/",MaxAge: year} - forums_w := httptest.NewRecorder() - forums_req := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) - forums_req_admin := forums_req - forums_req_admin.AddCookie(&admin_uid_cookie) - forums_req_admin.AddCookie(&admin_session_cookie) - forums_handler := http.HandlerFunc(route_forums) + forumsW := httptest.NewRecorder() + forumsReq := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) + forumsReqAdmin := forumsReq + forumsReqAdmin.AddCookie(&admin_uid_cookie) + forumsReqAdmin.AddCookie(&admin_session_cookie) + forumsHandler := http.HandlerFunc(route_forums) f, err := os.Create("cpu_forums_admin_parallel.prof") if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) for pb.Next() { - forums_w.Body.Reset() - forums_handler.ServeHTTP(forums_w,forums_req_admin) + forumsW.Body.Reset() + forumsHandler.ServeHTTP(forumsW,forumsReqAdmin) } pprof.StopCPUProfile() }) } -func BenchmarkForumsGuestRouteParallel(b *testing.B) { - b.ReportAllocs() - if !gloinited { - gloinit() - } - - b.RunParallel(func(pb *testing.PB) { - forums_w := httptest.NewRecorder() - forums_req := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) - forums_handler := http.HandlerFunc(route_forums) - for pb.Next() { - forums_w.Body.Reset() - forums_handler.ServeHTTP(forums_w,forums_req) - } - }) -} -*/ - -/*func BenchmarkRoutesSerial(b *testing.B) { +func BenchmarkRoutesSerial(b *testing.B) { b.ReportAllocs() admin, err := users.Get(1) if err != nil { diff --git a/install/install.go b/install/install.go index bbb0634d..13bbb01b 100644 --- a/install/install.go +++ b/install/install.go @@ -142,7 +142,7 @@ func init() { common.Config.SMTPPort = "25" // Misc - common.Config.DefaultRoute = "routeTopics" + common.Config.DefaultRoute = "routes.TopicList" common.Config.DefaultGroup = 3 // Should be a setting in the database common.Config.ActivationGroup = 5 // Should be a setting in the database common.Config.StaffCSS = "staff_post" diff --git a/langs/english.json b/langs/english.json index 007aafed..b7d641e2 100644 --- a/langs/english.json +++ b/langs/english.json @@ -126,6 +126,7 @@ "OperatingSystems": { "windows": "Microsoft Windows", "linux":"Linux", + "mac":"Apple Mac", "android": "Android", "iphone":"iPhone", "unknown":"Unknown" diff --git a/main.go b/main.go index 941ab30c..2aa8a27f 100644 --- a/main.go +++ b/main.go @@ -96,6 +96,10 @@ func afterDBInit() (err error) { if err != nil { return err } + common.TopicList, err = common.NewDefaultTopicList() + if err != nil { + return err + } common.GlobalViewCounter, err = common.NewGlobalViewCounter() if err != nil { diff --git a/query_gen/lib/mssql.go b/query_gen/lib/mssql.go index 834273d2..ef21ce1d 100644 --- a/query_gen/lib/mssql.go +++ b/query_gen/lib/mssql.go @@ -1135,6 +1135,9 @@ func _gen_mssql() (err error) { // Internal methods, not exposed in the interface func (adapter *MssqlAdapter) pushStatement(name string, stype string, querystr string) { + if name[0] == '_' { + return + } adapter.Buffer[name] = DBStmt{querystr, stype} adapter.BufferOrder = append(adapter.BufferOrder, name) } diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index 60288ab0..8686ff17 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -696,6 +696,9 @@ func _gen_mysql() (err error) { // Internal methods, not exposed in the interface func (adapter *MysqlAdapter) pushStatement(name string, stype string, querystr string) { + if name[0] == '_' { + return + } adapter.Buffer[name] = DBStmt{querystr, stype} adapter.BufferOrder = append(adapter.BufferOrder, name) } diff --git a/query_gen/lib/pgsql.go b/query_gen/lib/pgsql.go index 799186ba..fa676e12 100644 --- a/query_gen/lib/pgsql.go +++ b/query_gen/lib/pgsql.go @@ -393,6 +393,9 @@ func _gen_pgsql() (err error) { // Internal methods, not exposed in the interface func (adapter *PgsqlAdapter) pushStatement(name string, stype string, querystr string) { + if name[0] == '_' { + return + } adapter.Buffer[name] = DBStmt{querystr, stype} adapter.BufferOrder = append(adapter.BufferOrder, name) } diff --git a/query_gen/main.go b/query_gen/main.go index 91d7fd0e..52fa6574 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -255,10 +255,6 @@ func writeSelects(adapter qgen.Adapter) error { } func writeLeftJoins(adapter qgen.Adapter) error { - adapter.SimpleLeftJoin("getTopicList", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC", "") - - adapter.SimpleLeftJoin("getTopicReplies", "replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress", "replies.createdBy = users.uid", "tid = ?", "", "") - adapter.SimpleLeftJoin("getForumTopics", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "topics.parentID = ?", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc", "") adapter.SimpleLeftJoin("getProfileReplies", "users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy = users.uid", "users_replies.uid = ?", "", "") @@ -306,7 +302,7 @@ func writeUpdates(adapter qgen.Adapter) error { build.Update("updateTheme").Table("themes").Set("default = ?").Where("uname = ?").Parse() - build.Update("updateUser").Table("users").Set("name = ?, email = ?, group = ?").Where("uid = ?").Parse() + build.Update("updateUser").Table("users").Set("name = ?, email = ?, group = ?").Where("uid = ?").Parse() // TODO: Implement user_count for users_groups on things which use this build.Update("updateGroupPerms").Table("users_groups").Set("permissions = ?").Where("gid = ?").Parse() diff --git a/query_gen/tables.go b/query_gen/tables.go index 903abaf8..9965b948 100644 --- a/query_gen/tables.go +++ b/query_gen/tables.go @@ -48,6 +48,7 @@ func createTables(adapter qgen.Adapter) error { qgen.DBTableColumn{"is_mod", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"is_admin", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"is_banned", "boolean", 0, false, false, "0"}, + qgen.DBTableColumn{"user_count", "int", 0, false, false, "0"}, // TODO: Implement this qgen.DBTableColumn{"tag", "varchar", 50, false, false, "''"}, }, @@ -215,7 +216,9 @@ func createTables(adapter qgen.Adapter) error { qgen.DBTableColumn{"contentType", "varchar", 100, false, false, "replies"}, qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""}, }, - []qgen.DBTableKey{}, + []qgen.DBTableKey{ + qgen.DBTableKey{"reviseID", "primary"}, + }, ) qgen.Install.CreateTable("polls", "utf8mb4", "utf8mb4_general_ci", diff --git a/router_gen/main.go b/router_gen/main.go index 24a46b58..9ad0c57d 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -383,6 +383,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Deflect malformed requests if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { + //log.Print("len(req.URL.Path): ",len(req.URL.Path)) + //log.Print("req.URL.Path[0]: ",req.URL.Path[0]) + //log.Print("req.Host: ",req.Host) + //log.Print("common.Site.Host: ",common.Site.Host) + w.WriteHeader(200) // 400 w.Write([]byte("")) log.Print("Malformed Request") diff --git a/router_gen/routes.go b/router_gen/routes.go index 8381812a..7a256c0f 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -19,7 +19,7 @@ func routes() { addRouteGroup(reportGroup) topicGroup := newRouteGroup("/topics/", - View("routeTopics", "/topics/"), + View("routes.TopicList", "/topics/"), MemberView("routes.CreateTopic", "/topics/create/", "extraData"), ) addRouteGroup(topicGroup) diff --git a/routes.go b/routes.go index d6ed1ecc..4b658305 100644 --- a/routes.go +++ b/routes.go @@ -15,7 +15,6 @@ import ( "time" "./common" - "./query_gen/lib" ) // A blank list to fill out that parameter in Page for routes which don't use it @@ -45,140 +44,6 @@ func routeUploads() { func BadRoute() { } -func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - headerVars, ferr := common.UserCheck(w, r, &user) - if ferr != nil { - return ferr - } - headerVars.Zone = "topics" - headerVars.MetaDesc = headerVars.Settings["meta_desc"].(string) - - group, err := common.Groups.Get(user.Group) - if err != nil { - log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID) - return common.LocalError("Something weird happened", w, r, user) - } - - // TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins? - var canSee []int - if user.IsSuperAdmin { - canSee, err = common.Forums.GetAllVisibleIDs() - if err != nil { - return common.InternalError(err, w, r) - } - } else { - canSee = group.CanSee - } - - // We need a list of the visible forums for Quick Topic - // ? - Would it be useful, if we could post in social groups from /topics/? - var forumList []common.Forum - for _, fid := range canSee { - forum := common.Forums.DirtyGet(fid) - if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") { - fcopy := forum.Copy() - // TODO: Add a hook here for plugin_guilds - forumList = append(forumList, fcopy) - } - } - - // ? - Should we be showing plugin_guilds posts on /topics/? - argList, qlist := common.ForumListToArgQ(forumList) - - // ! Need an inline error not a page level error - if qlist == "" { - return common.NotFound(w, r) - } - - topicCount, err := common.ArgQToTopicCount(argList, qlist) - if err != nil { - return common.InternalError(err, w, r) - } - - // Get the current page - page, _ := strconv.Atoi(r.FormValue("page")) - offset, page, lastPage := common.PageOffset(topicCount, page, common.Config.ItemsPerPage) - - var topicList []*common.TopicsRow - stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?") - if err != nil { - return common.InternalError(err, w, r) - } - defer stmt.Close() - - argList = append(argList, offset) - argList = append(argList, common.Config.ItemsPerPage) - - rows, err := stmt.Query(argList...) - if err != nil { - return common.InternalError(err, w, r) - } - defer rows.Close() - - var reqUserList = make(map[int]bool) - for rows.Next() { - topicItem := common.TopicsRow{ID: 0} - err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount) - if err != nil { - return common.InternalError(err, w, r) - } - - topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID) - - forum := common.Forums.DirtyGet(topicItem.ParentID) - topicItem.ForumName = forum.Name - topicItem.ForumLink = forum.Link - - //topicItem.CreatedAt = common.RelativeTime(topicItem.CreatedAt) - topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt) - - if common.Vhooks["topics_topic_row_assign"] != nil { - common.RunVhook("topics_topic_row_assign", &topicItem, &forum) - } - topicList = append(topicList, &topicItem) - reqUserList[topicItem.CreatedBy] = true - reqUserList[topicItem.LastReplyBy] = true - } - err = rows.Err() - if err != nil { - return common.InternalError(err, w, r) - } - - // Convert the user ID map to a slice, then bulk load the users - var idSlice = make([]int, len(reqUserList)) - var i int - for userID := range reqUserList { - idSlice[i] = userID - i++ - } - - // TODO: What if a user is deleted via the Control Panel? - userList, err := common.Users.BulkGetMap(idSlice) - if err != nil { - return common.InternalError(err, w, r) - } - - // Second pass to the add the user data - // TODO: Use a pointer to TopicsRow instead of TopicsRow itself? - for _, topicItem := range topicList { - topicItem.Creator = userList[topicItem.CreatedBy] - topicItem.LastUser = userList[topicItem.LastReplyBy] - } - - pageList := common.Paginate(topicCount, common.Config.ItemsPerPage, 5) - pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum, pageList, page, lastPage} - if common.PreRenderHooks["pre_render_topic_list"] != nil { - if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) { - return nil - } - } - err = common.RunThemeTemplate(headerVars.Theme.Name, "topics", pi, w) - if err != nil { - return common.InternalError(err, w, r) - } - return nil -} - func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { page, _ := strconv.Atoi(r.FormValue("page")) diff --git a/routes/topic.go b/routes/topic.go index bb8dede3..50e7e4b5 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -18,6 +18,22 @@ import ( "../query_gen/lib" ) +type TopicStmts struct { + getReplies *sql.Stmt +} + +var topicStmts TopicStmts + +// TODO: Move these DbInits into a TopicList abstraction +func init() { + common.DbInits.Add(func(acc *qgen.Accumulator) error { + topicStmts = TopicStmts{ + getReplies: acc.SimpleLeftJoin("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?"), + } + return acc.FirstError() + }) +} + var successJSONBytes = []byte(`{"success":"1"}`) func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit string) common.RouteError { @@ -97,12 +113,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, poll, page, lastPage} // Get the replies.. - // TODO: Reuse this statement rather than preparing it on the spot, maybe via a TopicList abstraction - stmt, err := qgen.Builder.SimpleLeftJoin("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?") - if err != nil { - return common.InternalError(err, w, r) - } - rows, err := stmt.Query(topic.ID, offset, common.Config.ItemsPerPage) + rows, err := topicStmts.getReplies.Query(topic.ID, offset, common.Config.ItemsPerPage) if err == sql.ErrNoRows { return common.LocalError("Bad Page. Some of the posts may have been deleted or you got here by directly typing in the page number.", w, r, user) } else if err != nil { @@ -165,14 +176,16 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit if common.Vhooks["topic_reply_row_assign"] != nil { common.RunVhook("topic_reply_row_assign", &tpage, &replyItem) } - replyList = append(replyList, replyItem) + //replyList = append(replyList, replyItem) + // TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis? + tpage.ItemList = append(tpage.ItemList, replyItem) } err = rows.Err() if err != nil { return common.InternalError(err, w, r) } - tpage.ItemList = replyList + //tpage.ItemList = replyList if common.PreRenderHooks["pre_render_view_topic"] != nil { if common.RunPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) { return nil diff --git a/routes/topic_list.go b/routes/topic_list.go new file mode 100644 index 00000000..0aed72ae --- /dev/null +++ b/routes/topic_list.go @@ -0,0 +1,60 @@ +package routes + +import ( + "log" + "net/http" + "strconv" + + "../common" +) + +func TopicList(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + headerVars, ferr := common.UserCheck(w, r, &user) + if ferr != nil { + return ferr + } + headerVars.Zone = "topics" + headerVars.MetaDesc = headerVars.Settings["meta_desc"].(string) + + group, err := common.Groups.Get(user.Group) + if err != nil { + log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID) + return common.LocalError("Something weird happened", w, r, user) + } + + // Get the current page + page, _ := strconv.Atoi(r.FormValue("page")) + + // TODO: Pass a struct back rather than passing back so many variables + var topicList []*common.TopicsRow + var forumList []common.Forum + var pageList []int + var lastPage int + if user.IsSuperAdmin { + topicList, forumList, pageList, page, lastPage, err = common.TopicList.GetList(page) + } else { + topicList, forumList, pageList, page, lastPage, err = common.TopicList.GetListByGroup(group, page) + } + if err != nil { + return common.InternalError(err, w, r) + } + + // ! Need an inline error not a page level error + //log.Printf("topicList: %+v\n", topicList) + //log.Printf("forumList: %+v\n", forumList) + if len(topicList) == 0 { + return common.NotFound(w, r) + } + + pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum, pageList, page, lastPage} + if common.PreRenderHooks["pre_render_topic_list"] != nil { + if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) { + return nil + } + } + err = common.RunThemeTemplate(headerVars.Theme.Name, "topics", pi, w) + if err != nil { + return common.InternalError(err, w, r) + } + return nil +} diff --git a/schema/mssql/query_revisions.sql b/schema/mssql/query_revisions.sql index dcb01ed8..ccedd579 100644 --- a/schema/mssql/query_revisions.sql +++ b/schema/mssql/query_revisions.sql @@ -3,5 +3,6 @@ CREATE TABLE [revisions] ( [content] nvarchar (MAX) not null, [contentID] int not null, [contentType] nvarchar (100) DEFAULT 'replies' not null, - [createdAt] datetime not null + [createdAt] datetime not null, + primary key([reviseID]) ); \ No newline at end of file diff --git a/schema/mssql/query_users_groups.sql b/schema/mssql/query_users_groups.sql index 2efc3563..4e3f3f4f 100644 --- a/schema/mssql/query_users_groups.sql +++ b/schema/mssql/query_users_groups.sql @@ -6,6 +6,7 @@ CREATE TABLE [users_groups] ( [is_mod] bit DEFAULT 0 not null, [is_admin] bit DEFAULT 0 not null, [is_banned] bit DEFAULT 0 not null, + [user_count] int DEFAULT 0 not null, [tag] nvarchar (50) DEFAULT '' not null, primary key([gid]) ); \ No newline at end of file diff --git a/schema/mysql/query_revisions.sql b/schema/mysql/query_revisions.sql index b38d2eb1..c74c994f 100644 --- a/schema/mysql/query_revisions.sql +++ b/schema/mysql/query_revisions.sql @@ -3,5 +3,6 @@ CREATE TABLE `revisions` ( `content` text not null, `contentID` int not null, `contentType` varchar(100) DEFAULT 'replies' not null, - `createdAt` datetime not null + `createdAt` datetime not null, + primary key(`reviseID`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/mysql/query_users_groups.sql b/schema/mysql/query_users_groups.sql index ccdf0085..bc2cfaa5 100644 --- a/schema/mysql/query_users_groups.sql +++ b/schema/mysql/query_users_groups.sql @@ -6,6 +6,7 @@ CREATE TABLE `users_groups` ( `is_mod` boolean DEFAULT 0 not null, `is_admin` boolean DEFAULT 0 not null, `is_banned` boolean DEFAULT 0 not null, + `user_count` int DEFAULT 0 not null, `tag` varchar(50) DEFAULT '' not null, primary key(`gid`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/pgsql/query_revisions.sql b/schema/pgsql/query_revisions.sql index 73cec218..3e164e99 100644 --- a/schema/pgsql/query_revisions.sql +++ b/schema/pgsql/query_revisions.sql @@ -3,5 +3,6 @@ CREATE TABLE `revisions` ( `content` text not null, `contentID` int not null, `contentType` varchar (100) DEFAULT 'replies' not null, - `createdAt` timestamp not null + `createdAt` timestamp not null, + primary key(`reviseID`) ); \ No newline at end of file diff --git a/schema/pgsql/query_users_groups.sql b/schema/pgsql/query_users_groups.sql index 31888244..56757e83 100644 --- a/schema/pgsql/query_users_groups.sql +++ b/schema/pgsql/query_users_groups.sql @@ -6,6 +6,7 @@ CREATE TABLE `users_groups` ( `is_mod` boolean DEFAULT 0 not null, `is_admin` boolean DEFAULT 0 not null, `is_banned` boolean DEFAULT 0 not null, + `user_count` int DEFAULT 0 not null, `tag` varchar (50) DEFAULT '' not null, primary key(`gid`) ); \ No newline at end of file diff --git a/template_list.go b/template_list.go index 3b89c199..679ed331 100644 --- a/template_list.go +++ b/template_list.go @@ -126,177 +126,181 @@ var topic_20 = []byte(`' type="text" /> var topic_21 = []byte(` +`) +var topic_22 = []byte(`
+var topic_23 = []byte(`" style="background-image: url(`) +var topic_24 = []byte(`), url(/static/`) +var topic_25 = []byte(`/post-avatar-bg.jpg);background-position: 0px `) +var topic_26 = []byte(`-1`) +var topic_27 = []byte(`0px;background-repeat:no-repeat, repeat-y;">
`) -var topic_27 = []byte(` +var topic_28 = []byte(`
+var topic_29 = []byte(`_form" id="poll_option_`) +var topic_30 = []byte(`" name="poll_option_input" type="checkbox" value="`) +var topic_31 = []byte(`" /> `) -var topic_33 = []byte(` +var topic_33 = []byte(`" class="poll_option_text">`) +var topic_34 = []byte(`
`) -var topic_34 = []byte(` +var topic_35 = []byte(`
+var topic_36 = []byte(`_form" class="poll_vote_button">Vote +var topic_37 = []byte(`">Results
+var topic_38 = []byte(`" class="poll_results auto_hide">
+`) +var topic_39 = []byte(`
+var topic_40 = []byte(`" style="background-image: url(`) +var topic_41 = []byte(`), url(/static/`) +var topic_42 = []byte(`/post-avatar-bg.jpg);background-position: 0px `) +var topic_43 = []byte(`-1`) +var topic_44 = []byte(`0px;background-repeat:no-repeat, repeat-y;">

`) -var topic_43 = []byte(`

+var topic_45 = []byte(`

+var topic_46 = []byte(`    +var topic_47 = []byte(`" class="username real_username" rel="author">`) +var topic_48 = []byte(`   `) -var topic_47 = []byte(` +var topic_49 = []byte(` `) -var topic_55 = []byte(``) -var topic_57 = []byte(``) -var topic_60 = []byte(``) -var topic_63 = []byte(``) -var topic_66 = []byte(``) -var topic_69 = []byte(``) -var topic_72 = []byte(``) -var topic_75 = []byte(` +var topic_55 = []byte(` style="background-color:#D6FFD6;"`) +var topic_56 = []byte(`>`) +var topic_57 = []byte(``) +var topic_59 = []byte(``) +var topic_62 = []byte(``) +var topic_65 = []byte(``) +var topic_68 = []byte(``) +var topic_71 = []byte(``) +var topic_74 = []byte(``) +var topic_77 = []byte(` +var topic_78 = []byte(`?session=`) +var topic_79 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag this topic" aria-label="Flag this topic" rel="nofollow"> `) -var topic_78 = []byte(``) -var topic_80 = []byte(``) -var topic_81 = []byte(``) -var topic_82 = []byte(``) -var topic_83 = []byte(``) -var topic_84 = []byte(` +var topic_80 = []byte(``) +var topic_82 = []byte(``) +var topic_83 = []byte(``) +var topic_84 = []byte(``) +var topic_85 = []byte(``) +var topic_86 = []byte(`
`) -var topic_85 = []byte(` +var topic_87 = []byte(`
`) -var topic_86 = []byte(` +var topic_88 = []byte(` `) -var topic_87 = []byte(` +var topic_89 = []byte(`
`) -var topic_88 = []byte(` +var topic_90 = []byte(`
+var topic_91 = []byte(`" style="background-image: url(`) +var topic_92 = []byte(`), url(/static/`) +var topic_93 = []byte(`/post-avatar-bg.jpg);background-position: 0px `) +var topic_94 = []byte(`-1`) +var topic_95 = []byte(`0px;background-repeat:no-repeat, repeat-y;"> `) -var topic_94 = []byte(` +var topic_96 = []byte(`

`) -var topic_95 = []byte(`

+var topic_97 = []byte(`

   +var topic_98 = []byte(`" class="username real_username" rel="author">`) +var topic_99 = []byte(`   `) -var topic_98 = []byte(``) -var topic_103 = []byte(``) -var topic_106 = []byte(``) -var topic_109 = []byte(``) -var topic_111 = []byte(` +var topic_100 = []byte(``) +var topic_105 = []byte(``) +var topic_108 = []byte(``) +var topic_111 = []byte(``) +var topic_113 = []byte(` +var topic_114 = []byte(`?session=`) +var topic_115 = []byte(`&type=reply" class="mod_button report_item" title="Flag this reply" aria-label="Flag this reply" rel="nofollow"> `) -var topic_114 = []byte(``) -var topic_116 = []byte(``) -var topic_117 = []byte(``) -var topic_118 = []byte(``) -var topic_119 = []byte(``) -var topic_120 = []byte(` +var topic_116 = []byte(``) +var topic_118 = []byte(``) +var topic_119 = []byte(``) +var topic_120 = []byte(``) +var topic_121 = []byte(``) +var topic_122 = []byte(`
`) -var topic_121 = []byte(`
+var topic_123 = []byte(` `) -var topic_122 = []byte(` +var topic_124 = []byte(`
+var topic_125 = []byte(`" method="post"> +var topic_126 = []byte(`' type="hidden" />
@@ -317,16 +321,16 @@ var topic_124 = []byte(`' type="hidden" /> `) -var topic_125 = []byte(` +var topic_127 = []byte(`
`) -var topic_126 = []byte(` +var topic_128 = []byte(`
`) -var topic_127 = []byte(` +var topic_129 = []byte(` diff --git a/template_topic.go b/template_topic.go index 9ffef048..8a99b48f 100644 --- a/template_topic.go +++ b/template_topic.go @@ -127,22 +127,22 @@ w.Write([]byte(tmpl_topic_vars.Topic.Title)) w.Write(topic_20) } w.Write(topic_21) -w.Write([]byte(tmpl_topic_vars.Topic.ClassName)) +if tmpl_topic_vars.Poll.ID > 0 { w.Write(topic_22) -w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) +w.Write([]byte(tmpl_topic_vars.Topic.ClassName)) w.Write(topic_23) -w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) +w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) w.Write(topic_24) -if tmpl_topic_vars.Topic.ContentLines <= 5 { +w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) w.Write(topic_25) -} +if tmpl_topic_vars.Topic.ContentLines <= 5 { w.Write(topic_26) +} +w.Write(topic_27) if len(tmpl_topic_vars.Poll.QuickOptions) != 0 { for _, item := range tmpl_topic_vars.Poll.QuickOptions { -w.Write(topic_27) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write(topic_28) -w.Write([]byte(strconv.Itoa(item.ID))) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write(topic_29) w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_30) @@ -150,214 +150,218 @@ w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_31) w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_32) -w.Write([]byte(item.Value)) +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_33) -} -} +w.Write([]byte(item.Value)) w.Write(topic_34) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) +} +} w.Write(topic_35) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write(topic_36) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write(topic_37) -w.Write([]byte(tmpl_topic_vars.Topic.ClassName)) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write(topic_38) -w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) +} w.Write(topic_39) -w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) +w.Write([]byte(tmpl_topic_vars.Topic.ClassName)) w.Write(topic_40) -if tmpl_topic_vars.Topic.ContentLines <= 5 { +w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) w.Write(topic_41) -} +w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) w.Write(topic_42) -w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML)) +if tmpl_topic_vars.Topic.ContentLines <= 5 { w.Write(topic_43) -w.Write([]byte(tmpl_topic_vars.Topic.Content)) -w.Write(topic_44) -w.Write([]byte(tmpl_topic_vars.Topic.UserLink)) -w.Write(topic_45) -w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName)) -w.Write(topic_46) -if tmpl_topic_vars.CurrentUser.Perms.LikeItem { -w.Write(topic_47) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) -w.Write(topic_48) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) -w.Write(topic_49) -if tmpl_topic_vars.Topic.Liked { -w.Write(topic_50) -} else { -w.Write(topic_51) } -w.Write(topic_52) +w.Write(topic_44) +w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML)) +w.Write(topic_45) +w.Write([]byte(tmpl_topic_vars.Topic.Content)) +w.Write(topic_46) +w.Write([]byte(tmpl_topic_vars.Topic.UserLink)) +w.Write(topic_47) +w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName)) +w.Write(topic_48) +if tmpl_topic_vars.CurrentUser.Perms.LikeItem { +w.Write(topic_49) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +w.Write(topic_50) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_51) if tmpl_topic_vars.Topic.Liked { +w.Write(topic_52) +} else { w.Write(topic_53) } w.Write(topic_54) -} -if tmpl_topic_vars.CurrentUser.Perms.EditTopic { +if tmpl_topic_vars.Topic.Liked { w.Write(topic_55) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +} w.Write(topic_56) } -if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic { +if tmpl_topic_vars.CurrentUser.Perms.EditTopic { w.Write(topic_57) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write(topic_58) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +} +if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic { w.Write(topic_59) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +w.Write(topic_60) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_61) } if tmpl_topic_vars.CurrentUser.Perms.CloseTopic { if tmpl_topic_vars.Topic.IsClosed { -w.Write(topic_60) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) -w.Write(topic_61) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_62) -} else { -w.Write(topic_63) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) -w.Write(topic_64) +w.Write(topic_63) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_64) +} else { w.Write(topic_65) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +w.Write(topic_66) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_67) } } if tmpl_topic_vars.CurrentUser.Perms.PinTopic { if tmpl_topic_vars.Topic.Sticky { -w.Write(topic_66) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) -w.Write(topic_67) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_68) -} else { -w.Write(topic_69) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) -w.Write(topic_70) +w.Write(topic_69) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_70) +} else { w.Write(topic_71) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +w.Write(topic_72) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_73) } } if tmpl_topic_vars.CurrentUser.Perms.ViewIPs { -w.Write(topic_72) -w.Write([]byte(tmpl_topic_vars.Topic.IPAddress)) -w.Write(topic_73) -w.Write([]byte(tmpl_topic_vars.Topic.IPAddress)) w.Write(topic_74) -} +w.Write([]byte(tmpl_topic_vars.Topic.IPAddress)) w.Write(topic_75) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +w.Write([]byte(tmpl_topic_vars.Topic.IPAddress)) w.Write(topic_76) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +} w.Write(topic_77) -if tmpl_topic_vars.Topic.LikeCount > 0 { +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write(topic_78) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount))) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_79) +if tmpl_topic_vars.Topic.LikeCount > 0 { +w.Write(topic_80) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount))) +w.Write(topic_81) } if tmpl_topic_vars.Topic.Tag != "" { -w.Write(topic_80) -w.Write([]byte(tmpl_topic_vars.Topic.Tag)) -w.Write(topic_81) -} else { w.Write(topic_82) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level))) +w.Write([]byte(tmpl_topic_vars.Topic.Tag)) w.Write(topic_83) -} +} else { w.Write(topic_84) +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level))) +w.Write(topic_85) +} +w.Write(topic_86) if len(tmpl_topic_vars.ItemList) != 0 { for _, item := range tmpl_topic_vars.ItemList { if item.ActionType != "" { -w.Write(topic_85) -w.Write([]byte(item.ActionIcon)) -w.Write(topic_86) -w.Write([]byte(item.ActionType)) w.Write(topic_87) -} else { +w.Write([]byte(item.ActionIcon)) w.Write(topic_88) -w.Write([]byte(item.ClassName)) +w.Write([]byte(item.ActionType)) w.Write(topic_89) -w.Write([]byte(item.Avatar)) +} else { w.Write(topic_90) -w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) +w.Write([]byte(item.ClassName)) w.Write(topic_91) -if item.ContentLines <= 5 { +w.Write([]byte(item.Avatar)) w.Write(topic_92) -} +w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) w.Write(topic_93) +if item.ContentLines <= 5 { w.Write(topic_94) -w.Write([]byte(item.ContentHtml)) -w.Write(topic_95) -w.Write([]byte(item.UserLink)) -w.Write(topic_96) -w.Write([]byte(item.CreatedByName)) -w.Write(topic_97) -if tmpl_topic_vars.CurrentUser.Perms.LikeItem { -w.Write(topic_98) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_99) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) -w.Write(topic_100) -if item.Liked { -w.Write(topic_101) } +w.Write(topic_95) +w.Write(topic_96) +w.Write([]byte(item.ContentHtml)) +w.Write(topic_97) +w.Write([]byte(item.UserLink)) +w.Write(topic_98) +w.Write([]byte(item.CreatedByName)) +w.Write(topic_99) +if tmpl_topic_vars.CurrentUser.Perms.LikeItem { +w.Write(topic_100) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_101) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_102) +if item.Liked { +w.Write(topic_103) +} +w.Write(topic_104) } if tmpl_topic_vars.CurrentUser.Perms.EditReply { -w.Write(topic_103) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_104) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_105) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_106) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +w.Write(topic_107) } if tmpl_topic_vars.CurrentUser.Perms.DeleteReply { -w.Write(topic_106) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_107) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_108) -} -if tmpl_topic_vars.CurrentUser.Perms.ViewIPs { +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_109) -w.Write([]byte(item.IPAddress)) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_110) } +if tmpl_topic_vars.CurrentUser.Perms.ViewIPs { w.Write(topic_111) -w.Write([]byte(strconv.Itoa(item.ID))) +w.Write([]byte(item.IPAddress)) w.Write(topic_112) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +} w.Write(topic_113) -if item.LikeCount > 0 { +w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_114) -w.Write([]byte(strconv.Itoa(item.LikeCount))) +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_115) +if item.LikeCount > 0 { +w.Write(topic_116) +w.Write([]byte(strconv.Itoa(item.LikeCount))) +w.Write(topic_117) } if item.Tag != "" { -w.Write(topic_116) -w.Write([]byte(item.Tag)) -w.Write(topic_117) -} else { w.Write(topic_118) -w.Write([]byte(strconv.Itoa(item.Level))) +w.Write([]byte(item.Tag)) w.Write(topic_119) -} +} else { w.Write(topic_120) -} -} -} +w.Write([]byte(strconv.Itoa(item.Level))) w.Write(topic_121) -if tmpl_topic_vars.CurrentUser.Perms.CreateReply { +} w.Write(topic_122) -w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) +} +} +} w.Write(topic_123) -w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) +if tmpl_topic_vars.CurrentUser.Perms.CreateReply { w.Write(topic_124) -if tmpl_topic_vars.CurrentUser.Perms.UploadFiles { +w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write(topic_125) -} +w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write(topic_126) -} +if tmpl_topic_vars.CurrentUser.Perms.UploadFiles { w.Write(topic_127) +} +w.Write(topic_128) +} +w.Write(topic_129) w.Write(footer_0) w.Write([]byte(common.BuildWidget("footer",tmpl_topic_vars.Header))) w.Write(footer_1) diff --git a/templates/panel_analytics_agent_views.html b/templates/panel_analytics_agent_views.html index 9bcdef67..991aeafd 100644 --- a/templates/panel_analytics_agent_views.html +++ b/templates/panel_analytics_agent_views.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_agents.html b/templates/panel_analytics_agents.html index 218ab955..44d2bc4e 100644 --- a/templates/panel_analytics_agents.html +++ b/templates/panel_analytics_agents.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_posts.html b/templates/panel_analytics_posts.html index fcb8f2fc..1aa02f2a 100644 --- a/templates/panel_analytics_posts.html +++ b/templates/panel_analytics_posts.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_referrer_views.html b/templates/panel_analytics_referrer_views.html index 5d419a54..2946cc84 100644 --- a/templates/panel_analytics_referrer_views.html +++ b/templates/panel_analytics_referrer_views.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_referrers.html b/templates/panel_analytics_referrers.html index 25e1352c..9ad69dc4 100644 --- a/templates/panel_analytics_referrers.html +++ b/templates/panel_analytics_referrers.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_route_views.html b/templates/panel_analytics_route_views.html index 36bbf611..15ac0bce 100644 --- a/templates/panel_analytics_route_views.html +++ b/templates/panel_analytics_route_views.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_routes.html b/templates/panel_analytics_routes.html index 7f3b1361..ed960cc1 100644 --- a/templates/panel_analytics_routes.html +++ b/templates/panel_analytics_routes.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_system_views.html b/templates/panel_analytics_system_views.html index 9633b86f..4cddc96a 100644 --- a/templates/panel_analytics_system_views.html +++ b/templates/panel_analytics_system_views.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_systems.html b/templates/panel_analytics_systems.html index 36997967..92c683b0 100644 --- a/templates/panel_analytics_systems.html +++ b/templates/panel_analytics_systems.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_topics.html b/templates/panel_analytics_topics.html index 90e9692d..f69b1f32 100644 --- a/templates/panel_analytics_topics.html +++ b/templates/panel_analytics_topics.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/panel_analytics_views.html b/templates/panel_analytics_views.html index 06b34ffa..fe819c1a 100644 --- a/templates/panel_analytics_views.html +++ b/templates/panel_analytics_views.html @@ -1,7 +1,7 @@ {{template "header.html" . }}
{{template "panel-menu.html" . }} -
+
diff --git a/templates/topic.html b/templates/topic.html index 63d17de2..2d828d1e 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -21,6 +21,7 @@ {{end}}
+{{if .Poll.ID}}
@@ -44,6 +45,7 @@
+{{end}}
diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index f03036b9..c9a2feff 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -917,6 +917,9 @@ textarea { margin-top: 0px; } +.postImage { + width: 100%; +} .post_item { display: flex; margin-bottom: 16px; diff --git a/themes/shadow/public/panel.css b/themes/shadow/public/panel.css index 6a3ef29e..8514333f 100644 --- a/themes/shadow/public/panel.css +++ b/themes/shadow/public/panel.css @@ -1,3 +1,16 @@ +.submenu a { + margin-left: 4px; +} +.colstack_head .rowitem { + display: flex; +} +.colstack_head .rowitem h1, .colstack_head .rowitem a { + margin-right: auto; +} +.colstack_head .rowitem a h1 { + margin-right: 0px; +} + .rowlist .tag-mini { font-size: 10px; margin-left: 2px;