Added support for HTTP/2 Push.

The themes are still going to be a little broken for a while, but here's the progress I've made.

Renamed the topics_trow_assign hook to topics_topic_row_assign.
Added more debug code to the generated router.
Added a robots.txt file.
Gosora now responds to favicon.ico requests with a 404 rather than the topic list.
Fixed the tests and some of the benchmarks.
Changed the default UserCacheCapacity from 100 to 120.
Changed the default TopicCacheCapacity from 100 to 200.
Added the last replyer to the topics list and the forum pages.
Added the BulkCascadeGetMap method to the UserStore.
Refactored the topics list and forum page to load the users with a call to the UserStore rather than via a join.
WebSockets now work on SSL.
Fixed a race condition when the user permissions are initialised at the start of a request.
Fixed a race condition when the user permissions for the OP of a topic are initialised.
The rows.Close() calls are deferred once more, so that we can catch problems with recover()
Improved support for struct pointers in the template compiler.
Added a pin emoji to pinned topics to make them stand-out on the Shadow theme, we have some other ideas in mind for this, but I'd like to get Shadow fully functional for this commit.
Fixed a bug an issue with Chrome not detecting XHTML style closes on <form>s.
Fixed a visual issue with `color` not being set for textarea elements for the Shadow theme.
Fixed a function which wasn't getting renamed for PGSQL.

Added seven new UserStore tests.
This commit is contained in:
Azareal 2017-08-06 16:22:18 +01:00
parent 41c620b988
commit f41c281e43
33 changed files with 679 additions and 201 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
tmp/*
tmp2/*
cert_test/*
uploads/avatar_*
uploads/socialgroup_*
bin/*

View File

@ -18,7 +18,8 @@ var vhooks map[string]func(...interface{})interface{} = map[string]func(...inter
"forum_check_pre_perms": nil,
"intercept_build_widgets": nil,
"forum_trow_assign": nil,
"topics_trow_assign": nil,
"topics_topic_row_assign": nil,
//"topics_user_row_assign": nil,
"create_group_preappend": nil, // What is this? Investigate!
"topic_create_pre_loop": nil,
}

View File

@ -39,8 +39,8 @@ var get_topic_basic_stmt *sql.Stmt
var get_activity_entry_stmt *sql.Stmt
var forum_entry_exists_stmt *sql.Stmt
var group_entry_exists_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt
var get_forum_topics_offset_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt
var get_topic_list_stmt *sql.Stmt
var get_topic_user_stmt *sql.Stmt
var get_topic_by_reply_stmt *sql.Stmt
@ -310,14 +310,14 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing get_topic_replies_offset statement.")
get_topic_replies_offset_stmt, 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`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?")
log.Print("Preparing get_forum_topics_offset statement.")
get_forum_topics_offset_stmt, err = db.Prepare("SELECT `tid`,`title`,`content`,`createdBy`,`is_closed`,`sticky`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`parentID`,`postCount`,`likeCount` FROM `topics` WHERE `parentID` = ? ORDER BY sticky DESC,lastReplyAt DESC,createdBy DESC LIMIT ?,?")
if err != nil {
return err
}
log.Print("Preparing get_forum_topics_offset statement.")
get_forum_topics_offset_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`lastReplyAt`,`topics`.`parentID`,`topics`.`postCount`,`topics`.`likeCount`,`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 LIMIT ?,?")
log.Print("Preparing get_topic_replies_offset statement.")
get_topic_replies_offset_stmt, 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`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?")
if err != nil {
return err
}
@ -365,7 +365,7 @@ func _gen_mysql() (err error) {
}
log.Print("Preparing create_topic statement.")
create_topic_stmt, err = db.Prepare("INSERT INTO `topics`(`parentID`,`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`ipaddress`,`words`,`createdBy`) VALUES (?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?)")
create_topic_stmt, err = db.Prepare("INSERT INTO `topics`(`parentID`,`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`ipaddress`,`words`,`createdBy`) VALUES (?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?)")
if err != nil {
return err
}
@ -473,7 +473,7 @@ func _gen_mysql() (err error) {
}
log.Print("Preparing add_replies_to_topic statement.")
add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyAt` = UTC_TIMESTAMP() WHERE `tid` = ?")
add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyBy` = ?,`lastReplyAt` = UTC_TIMESTAMP() WHERE `tid` = ?")
if err != nil {
return err
}

View File

@ -50,7 +50,7 @@ func _gen_pgsql() (err error) {
}
log.Print("Preparing add_replies_to_topic statement.")
add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyAt` = UTC_TIMESTAMP() WHERE `tid` = ?")
add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyBy` = ?,`lastReplyAt` = LOCALTIMESTAMP() WHERE `tid` = ?")
if err != nil {
return err
}
@ -74,7 +74,7 @@ func _gen_pgsql() (err error) {
}
log.Print("Preparing update_forum_cache statement.")
update_forum_cache_stmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = UTC_TIMESTAMP() WHERE `fid` = ?")
update_forum_cache_stmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = LOCALTIMESTAMP() WHERE `fid` = ?")
if err != nil {
return err
}

View File

@ -62,9 +62,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
}
//fmt.Println("prefix:",prefix)
//fmt.Println("req.URL.Path:",req.URL.Path)
//fmt.Println("extra_data:",extra_data)
if dev.SuperDebug {
fmt.Println("before route_static")
fmt.Println("prefix:",prefix)
fmt.Println("req.URL.Path:",req.URL.Path)
fmt.Println("extra_data:",extra_data)
}
if prefix == "/static" {
req.URL.Path += extra_data
@ -207,6 +211,18 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.UploadHandler(w,req)
return
case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list
// TO-DO: Add support for favicons and robots.txt files
switch(extra_data) {
case "robots.txt":
route_robots_txt(w,req)
return
}
if extra_data != "" {
NotFound(w,req)
return
}
config.DefaultRoute(w,req,user)
return
//default: NotFound(w,req)

View File

@ -57,16 +57,16 @@ func gloinit() {
}
if config.CacheTopicUser == CACHE_STATIC {
users = NewMemoryUserStore(user_cache_capacity)
topics = NewMemoryTopicStore(topic_cache_capacity)
users = NewMemoryUserStore(config.UserCacheCapacity)
topics = NewMemoryTopicStore(config.TopicCacheCapacity)
} else {
users = NewSqlUserStore()
topics = NewSqlTopicStore()
}
init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
//log.SetOutput(os.Stdout)
auth = NewDefaultAuth()
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
gloinited = true
@ -82,19 +82,19 @@ func BenchmarkTopicTemplateSerial(b *testing.B) {
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","",-1,58,"127.0.0.1"}
topic := TopicUser{Title: "Lol",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"}
topic := TopicUser{Title: "Lol",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,CreatedByName:"Admin",ClassName: "",Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"}
var replyList []Reply
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry","Jerry",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry2","Jerry2",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry3","Jerry3",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry4","Jerry4",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry5","Jerry5",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry6","Jerry6",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry7","Jerry7",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry8","Jerry8",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry9","Jerry9",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry10","Jerry10",config.DefaultGroup,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry","Jerry",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry2","Jerry2",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry3","Jerry3",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry4","Jerry4",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry5","Jerry5",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry6","Jerry6",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry7","Jerry7",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry8","Jerry8",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry9","Jerry9",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
replyList = append(replyList, Reply{0,0,"Hey everyone!","Hey everyone!",0,"jerry10","Jerry10",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
headerVars := HeaderVars{
NoticeList:[]string{"test"},
@ -103,6 +103,7 @@ func BenchmarkTopicTemplateSerial(b *testing.B) {
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
Site:site,
}
tpage := TopicPage{"Topic Blah",user,headerVars,replyList,topic,1,1,extData}
@ -167,20 +168,20 @@ func BenchmarkTopicTemplateSerial(b *testing.B) {
func BenchmarkTopicsTemplateSerial(b *testing.B) {
b.ReportAllocs()
user := User{0,"bob","Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,"admin-alice","Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","",-1,58,"127.0.0.1"}
user := User{0,build_profile_url("bob",0),"Bob","bob@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"127.0.0.1"}
admin := User{1,build_profile_url("admin-alice",1),"Admin Alice","admin@localhost",0,true,true,true,true,true,false,AllPerms,make(map[string]bool),"",false,"","","","","Admin",58,580,"127.0.0.1"}
var topicList []TopicsRow
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
topicList = append(topicList, TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,UserLink:build_profile_url("admin-alice",1),CreatedByName:"Admin Alice",Css: no_css_tmpl,Tag: "Admin", Level: 58, IpAddress: "127.0.0.1"})
var topicList []*TopicsRow
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
topicList = append(topicList, &TopicsRow{Title: "Hey everyone!",Content: "Hey everyone!",CreatedBy: 1,CreatedAt: "0000-00-00 00:00:00",ParentID: 1,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
headerVars := HeaderVars{
NoticeList:[]string{"test"},
@ -189,6 +190,7 @@ func BenchmarkTopicsTemplateSerial(b *testing.B) {
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
Site:site,
}
w := ioutil.Discard
@ -242,8 +244,7 @@ func BenchmarkStaticRouteParallel(b *testing.B) {
})
}
// TO-DO: Make these routes compatible with the changes to the router
/*
// TO-DO: Swap out LocalError for a panic for this?
func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.ReportAllocs()
if !gloinited {
@ -253,10 +254,10 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
admin, err := users.CascadeGet(1)
if err != nil {
panic(err)
b.Fatal(err)
}
if !admin.Is_Admin {
panic("UID1 is not an admin")
b.Fatal("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}
@ -266,11 +267,16 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
topic_req_admin := topic_req
topic_req_admin.AddCookie(&admin_uid_cookie)
topic_req_admin.AddCookie(&admin_session_cookie)
topic_handler := http.HandlerFunc(route_topic_id)
// Deal with the session stuff, etc.
user, ok := PreRoute(topic_w,topic_req_admin)
if !ok {
b.Fatal("Mysterious error!")
}
for pb.Next() {
topic_w.Body.Reset()
topic_handler.ServeHTTP(topic_w,topic_req_admin)
route_topic_id(topic_w,topic_req_admin,user)
}
})
}
@ -284,14 +290,15 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
topic_w := httptest.NewRecorder()
topic_req := httptest.NewRequest("get","/topic/1",bytes.NewReader(nil))
topic_handler := http.HandlerFunc(route_topic_id)
for pb.Next() {
topic_w.Body.Reset()
topic_handler.ServeHTTP(topic_w,topic_req)
route_topic_id(topic_w,topic_req,guest_user)
}
})
}
// TO-DO: Make these routes compatible with the changes to the router
/*
func BenchmarkForumsAdminRouteParallel(b *testing.B) {
b.ReportAllocs()
if !gloinited {
@ -619,7 +626,7 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
}
b.RunParallel(func(pb *testing.PB) {
tu := TopicUser{Css: no_css_tmpl}
var tu TopicUser
for pb.Next() {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows {
@ -640,7 +647,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
}
b.RunParallel(func(pb *testing.PB) {
tu := TopicUser{Css: no_css_tmpl}
var tu TopicUser
for pb.Next() {
err := get_topic_user_stmt.QueryRow(1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
if err == ErrNoRows {
@ -656,7 +663,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
func BenchmarkQueriesSerial(b *testing.B) {
b.ReportAllocs()
tu := TopicUser{Css: no_css_tmpl}
var tu TopicUser
b.Run("topic", func(b *testing.B) {
for i := 0; i < b.N; i++ {
err := db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level from topics left join users ON topics.createdBy = users.uid where tid = ?", 1).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
@ -686,7 +693,7 @@ func BenchmarkQueriesSerial(b *testing.B) {
}
})
replyItem := Reply{Css: no_css_tmpl}
var replyItem Reply
var is_super_admin bool
var group int
b.Run("topic_replies_scan", func(b *testing.B) {

View File

@ -160,8 +160,8 @@ config.MaxRequestSize = 5 * megabyte
// Caching
config.CacheTopicUser = CACHE_STATIC
config.UserCacheCapacity = 100 // The max number of users held in memory
config.TopicCacheCapacity = 100 // The max number of topics held in memory
config.UserCacheCapacity = 120 // The max number of users held in memory
config.TopicCacheCapacity = 200 // The max number of topics held in memory
// Email
config.SmtpServer = ""

19
main.go
View File

@ -32,7 +32,9 @@ var startTime time.Time
var templates = template.New("")
//var no_css_tmpl template.CSS = template.CSS("")
var settings map[string]interface{} = make(map[string]interface{})
var external_sites map[string]string = make(map[string]string)
var external_sites map[string]string = map[string]string{
"YT":"https://www.youtube.com/",
}
var groups []Group
var groupCapCount int
var static_files map[string]SFile = make(map[string]SFile)
@ -48,6 +50,9 @@ var template_create_topic_handle func(CreateTopicPage,io.Writer) = nil
func compile_templates() error {
var c CTemplateSet
user := User{62,build_profile_url("fake-user",62),"Fake User","compiler@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"0.0.0.0.0"}
// TO-DO: Do a more accurate level calculation for this?
user2 := User{1,build_profile_url("admin-alice",1),"Admin Alice","alice@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",58,1000,"127.0.0.1"}
user3 := User{2,build_profile_url("admin-fred",62),"Admin Fred","fred@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",42,900,"::1"}
headerVars := HeaderVars{
Site:site,
NoticeList:[]string{"test"},
@ -88,15 +93,15 @@ func compile_templates() error {
forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData}
forums_tmpl := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList)
var topicsList []TopicsRow
topicsList = append(topicsList,TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","admin-alice","Admin Alice","","",0,"","","","",58,"General","/forum/general.2"})
var topicsList []*TopicsRow
topicsList = append(topicsList,&TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",user3.ID,1,"","127.0.0.1",0,1,"classname","",&user2,"",0,&user3,"General","/forum/general.2"})
topics_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData}
topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList)
var topicList []TopicUser
topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
//var topicList []TopicUser
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
forum_item := Forum{1,"general","General Forum","Where the general stuff happens",true,"all",0,"",0,"","",0,"",0,""}
forum_page := ForumPage{"General Forum",user,headerVars,topicList,forum_item,1,1,extData}
forum_page := ForumPage{"General Forum",user,headerVars,topicsList,forum_item,1,1,extData}
forum_tmpl := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList)
log.Print("Writing the templates")
@ -191,7 +196,6 @@ func main(){
}
init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
log.Print("Initialising the widgets")
err = init_widgets()
@ -298,6 +302,7 @@ func main(){
// pprof.StopCPUProfile()
//}
// TO-DO: Let users run *both* HTTP and HTTPS
log.Print("Initialising the HTTP server")
if !site.EnableSsl {
if site.Port == "" {

View File

@ -37,7 +37,42 @@ func TestUserStore(t *testing.T) {
}
if user.ID != 1 {
t.Error("user.ID doesn't not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
}
// TO-DO: Lock onto the specific error type. Is this even possible without sacrificing the detailed information in the error message?
var userList map[int]*User
userList, err = users.BulkCascadeGetMap([]int{-1})
if err == nil {
t.Error("UID #-1 shouldn't exist")
}
userList, err = users.BulkCascadeGetMap([]int{0})
if err == nil {
t.Error("UID #0 shouldn't exist")
}
userList, err = users.BulkCascadeGetMap([]int{1})
if err == ErrNoRows {
t.Error("Couldn't find UID #1")
} else if err != nil {
t.Fatal(err)
}
if len(userList) == 0 {
t.Error("The returned map is empty for UID #0")
} else if len(userList) > 1 {
t.Error("Too many results were returned for UID #0")
}
user, ok := userList[1]
if !ok {
t.Error("We couldn't find UID #0 in the returned map")
t.Error("userList",userList)
}
if user.ID != 1 {
t.Error("user.ID does not match the requested UID. Got '" + strconv.Itoa(user.ID) + "' instead.")
}
}

View File

@ -49,7 +49,7 @@ CREATE TABLE `topics`(
`parsed_content` text not null,
`createdAt` datetime not null,
`lastReplyAt` datetime not null,
/*`lastReplyBy` int not null,*/
`lastReplyBy` int not null,
`createdBy` int not null,
`is_closed` tinyint DEFAULT 0 not null,
`sticky` tinyint DEFAULT 0 not null,
@ -243,8 +243,8 @@ INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (3,1,'{}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (4,1,'{}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (5,1,'{}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (6,1,'{}');
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`)
VALUES ('Test Topic','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,2);
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`)
VALUES ('Test Topic','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2);
INSERT INTO replies(`tid`,`content`,`createdAt`,`createdBy`,`lastEdit`,`lastEditBy`)
VALUES (1,'Reply 1',UTC_TIMESTAMP(),1,0,0);

View File

@ -61,7 +61,7 @@ type TopicsPage struct
Title string
CurrentUser User
Header HeaderVars
ItemList []TopicsRow
ItemList []*TopicsRow
ExtData ExtData
}
@ -70,7 +70,7 @@ type ForumPage struct
Title string
CurrentUser User
Header HeaderVars
ItemList []TopicUser
ItemList []*TopicsRow
Forum Forum
Page int
LastPage int

View File

@ -965,6 +965,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
}
defer rows.Close()
// TO-DO: Add a UserStore method for iterating over global users
for rows.Next() {
puser := User{ID: 0,}
err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.Is_Super_Admin, &puser.Avatar)

View File

@ -55,7 +55,7 @@ type SocialGroupPage struct
Title string
CurrentUser User
Header HeaderVars
ItemList []TopicUser
ItemList []*TopicsRow
Forum Forum
SocialGroup SocialGroup
Page int

View File

@ -3,6 +3,7 @@ package main
import "strconv"
import "testing"
// TO-DO: Replace the soft tabs with hard ones
// go test -v
type ME_Pair struct
@ -16,6 +17,7 @@ func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair {
}
func TestBBCodeRender(t *testing.T) {
//t.Skip()
var res string
var msgList []ME_Pair
msgList = addMEPair(msgList,"hi","hi")
@ -27,10 +29,12 @@ func TestBBCodeRender(t *testing.T) {
msgList = addMEPair(msgList,"[i]hi[/i]","<i>hi</i>")
msgList = addMEPair(msgList,"[s]hi[/s]","<s>hi</s>")
msgList = addMEPair(msgList,"[c]hi[/c]","[c]hi[/c]")
msgList = addMEPair(msgList,"[b]hi[/i]","[b]hi[/i]")
msgList = addMEPair(msgList,"[/b]hi[b]","[/b]hi[b]")
msgList = addMEPair(msgList,"[/b]hi[/b]","[/b]hi[/b]")
msgList = addMEPair(msgList,"[b][b]hi[/b]","<b>hi</b>")
if !testing.Short() {
msgList = addMEPair(msgList,"[b]hi[/i]","[b]hi[/i]")
msgList = addMEPair(msgList,"[/b]hi[b]","[/b]hi[b]")
msgList = addMEPair(msgList,"[/b]hi[/b]","[/b]hi[/b]")
msgList = addMEPair(msgList,"[b][b]hi[/b]","<b>hi</b>")
}
msgList = addMEPair(msgList,"[b][b]hi","[b][b]hi")
msgList = addMEPair(msgList,"[b][b][b]hi","[b][b][b]hi")
msgList = addMEPair(msgList,"[/b]hi","[/b]hi")
@ -185,6 +189,7 @@ func TestBBCodeRender(t *testing.T) {
}
func TestMarkdownRender(t *testing.T) {
//t.Skip()
var res string
var msgList []ME_Pair
msgList = addMEPair(msgList,"hi","hi")

View File

@ -95,7 +95,10 @@ $(document).ready(function(){
}
if(window["WebSocket"]) {
conn = new WebSocket("ws://" + document.location.host + "/ws/");
if(window.location.protocol == "https:")
conn = new WebSocket("wss://" + document.location.host + "/ws/");
else conn = new WebSocket("ws://" + document.location.host + "/ws/");
conn.onopen = function() {
conn.send("page " + document.location.pathname + '\r');
}

View File

@ -1,4 +1,4 @@
/* WIP Under *Heavy* Construction */
/* WIP Under Really Heavy Construction */
package qgen
import "strings"
@ -144,7 +144,13 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
querystr += "`" + item.Column + "` ="
for _, token := range item.Expr {
switch(token.Type) {
case "function","operator","number","substitute":
case "function":
// TO-DO: Write a more sophisticated function parser on the utils side.
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()"
}
querystr += " " + token.Contents + ""
case "operator","number","substitute":
querystr += " " + token.Contents + ""
case "column":
querystr += " `" + token.Contents + "`"
@ -166,8 +172,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
switch(token.Type) {
case "function":
// TO-DO: Write a more sophisticated function parser on the utils side. What's the situation in regards to case sensitivity?
// TO-DO: Change things on the MySQL and Gosora side to timestamps
if token.Contents == "UTC_TIMESTAMP()" {
if strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()"
}
querystr += " " + token.Contents + ""

View File

@ -108,12 +108,12 @@ type DB_Adapter interface {
GetName() string
CreateTable(name string, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (string, error)
SimpleInsert(name string, table string, columns string, fields string) (string, error)
SimpleReplace(string,string,string,string) (string, error)
SimpleUpdate(string,string,string,string) (string, error)
SimpleDelete(string,string,string) (string, error)
Purge(string,string) (string, error)
SimpleSelect(string,string,string,string,string,string) (string, error)
SimpleLeftJoin(string,string,string,string,string,string,string,string) (string, error)
SimpleReplace(name string, table string, columns string, fields string) (string, error)
SimpleUpdate(name string, table string, set string, where string) (string, error)
SimpleDelete(name string, table string, where string) (string, error)
Purge(name string, table string) (string, error)
SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error)
SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error)
SimpleInnerJoin(string,string,string,string,string,string,string,string) (string, error)
SimpleInsertSelect(string,DB_Insert,DB_Select) (string,error)
SimpleInsertLeftJoin(string,DB_Insert,DB_Join) (string,error)

View File

@ -223,14 +223,14 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("group_entry_exists","users_groups","gid","name = ''","gid ASC","0,1")
adapter.SimpleSelect("get_forum_topics_offset","topics","tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount","parentID = ?","sticky DESC, lastReplyAt DESC, createdBy DESC","?,?")
return nil
}
func write_left_joins(adapter qgen.DB_Adapter) error {
adapter.SimpleLeftJoin("get_topic_replies_offset","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","tid = ?","","?,?")
adapter.SimpleLeftJoin("get_forum_topics_offset","topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, topics.postCount, topics.likeCount, users.name, users.avatar","topics.createdBy = users.uid","topics.parentID = ?","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC","?,?")
adapter.SimpleLeftJoin("get_topic_list","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("get_topic_user","topics","users","topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level","topics.createdBy = users.uid","tid = ?","","")
@ -253,7 +253,7 @@ func write_inner_joins(adapter qgen.DB_Adapter) error {
}
func write_inserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("create_topic","topics","parentID,title,content,parsed_content,createdAt,lastReplyAt,ipaddress,words,createdBy","?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?")
adapter.SimpleInsert("create_topic","topics","parentID,title,content,parsed_content,createdAt,lastReplyAt,lastReplyBy,ipaddress,words,createdBy","?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?,?")
adapter.SimpleInsert("create_report","topics","title,content,parsed_content,createdAt,lastReplyAt,createdBy,data,parentID,css_class","?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,1,'report'")
@ -298,7 +298,7 @@ func write_replaces(adapter qgen.DB_Adapter) error {
}
func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("add_replies_to_topic","topics","postCount = postCount + ?, lastReplyAt = UTC_TIMESTAMP()","tid = ?")
adapter.SimpleUpdate("add_replies_to_topic","topics","postCount = postCount + ?, lastReplyBy = ?, lastReplyAt = UTC_TIMESTAMP()","tid = ?")
adapter.SimpleUpdate("remove_replies_from_topic","topics","postCount = postCount - ?","tid = ?")

View File

@ -140,9 +140,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
}
//fmt.Println("prefix:",prefix)
//fmt.Println("req.URL.Path:",req.URL.Path)
//fmt.Println("extra_data:",extra_data)
if dev.SuperDebug {
fmt.Println("before route_static")
fmt.Println("prefix:",prefix)
fmt.Println("req.URL.Path:",req.URL.Path)
fmt.Println("extra_data:",extra_data)
}
if prefix == "/static" {
req.URL.Path += extra_data
@ -174,6 +178,18 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.UploadHandler(w,req)
return
case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list
// TO-DO: Add support for favicons and robots.txt files
switch(extra_data) {
case "robots.txt":
route_robots_txt(w,req)
return
}
if extra_data != "" {
NotFound(w,req)
return
}
config.DefaultRoute(w,req,user)
return
//default: NotFound(w,req)

117
routes.go
View File

@ -70,6 +70,18 @@ func route_fstatic(w http.ResponseWriter, r *http.Request){
http.ServeFile(w,r,r.URL.Path)
}*/
// TO-DO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TO-DO: Add a sitemap
// TO-DO: Add an API so that plugins can register disallowed areas. E.g. /groups/join for plugin_socialgroups
func route_robots_txt(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`User-agent: *
Disallow: /panel/
Disallow: /topics/create/
Disallow: /user/edit/
Disallow: /accounts/
`))
}
func route_overview(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
@ -134,8 +146,9 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User){
}
qlist = qlist[0:len(qlist) - 1]
var topicList []TopicsRow
stmt, err := qgen.Builder.SimpleLeftJoin("topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, topics.postCount, topics.likeCount, users.name, users.avatar","topics.createdBy = users.uid","parentID IN("+qlist+")","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC","")
var topicList []*TopicsRow
//stmt, err := qgen.Builder.SimpleLeftJoin("topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, topics.postCount, topics.likeCount, users.name, users.avatar","topics.createdBy = users.uid","parentID IN("+qlist+")","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC","")
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 {
InternalError(err,w,r)
return
@ -146,25 +159,18 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r)
return
}
defer rows.Close()
topicItem := TopicsRow{ID: 0}
var reqUserList map[int]bool = make(map[int]bool)
for rows.Next() {
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.Is_Closed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount, &topicItem.CreatedByName, &topicItem.Avatar)
topicItem := TopicsRow{ID: 0}
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.Is_Closed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
if err != nil {
InternalError(err,w,r)
return
}
topicItem.Link = build_topic_url(name_to_slug(topicItem.Title),topicItem.ID)
topicItem.UserLink = build_profile_url(name_to_slug(topicItem.CreatedByName),topicItem.CreatedBy)
if topicItem.Avatar != "" {
if topicItem.Avatar[0] == '.' {
topicItem.Avatar = "/uploads/avatar_" + strconv.Itoa(topicItem.CreatedBy) + topicItem.Avatar
}
} else {
topicItem.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(topicItem.CreatedBy),1)
}
forum := fstore.DirtyGet(topicItem.ParentID)
if topicItem.ParentID >= 0 {
@ -183,17 +189,40 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r)
}
if hooks["topics_trow_assign"] != nil {
run_vhook("topics_trow_assign", &topicItem, &forum)
if hooks["topics_topic_row_assign"] != nil {
run_vhook("topics_topic_row_assign", &topicItem, &forum)
}
topicList = append(topicList, topicItem)
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
}
err = rows.Err()
if err != nil {
InternalError(err,w,r)
return
}
rows.Close()
// Convert the user ID map to a slice, then bulk load the users
var idSlice []int = make([]int,len(reqUserList))
var i int
for userID, _ := range reqUserList {
idSlice[i] = userID
i++
}
// TO-DO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice)
if err != nil {
InternalError(err,w,r)
return
}
// Second pass to the add the user data
// TO-DO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pi := TopicsPage{"Topic List",user,headerVars,topicList,extData}
if pre_render_hooks["pre_render_topic_list"] != nil {
@ -268,27 +297,20 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
InternalError(err,w,r)
return
}
defer rows.Close()
var topicList []TopicUser
var topicItem TopicUser = TopicUser{ID: 0}
// TO-DO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item?
var topicList []*TopicsRow
var reqUserList map[int]bool = make(map[int]bool)
for rows.Next() {
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.Is_Closed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount, &topicItem.CreatedByName, &topicItem.Avatar)
var topicItem TopicsRow = TopicsRow{ID: 0}
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.Is_Closed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
if err != nil {
InternalError(err,w,r)
return
}
topicItem.Link = build_topic_url(name_to_slug(topicItem.Title),topicItem.ID)
topicItem.UserLink = build_profile_url(name_to_slug(topicItem.CreatedByName),topicItem.CreatedBy)
if topicItem.Avatar != "" {
if topicItem.Avatar[0] == '.' {
topicItem.Avatar = "/uploads/avatar_" + strconv.Itoa(topicItem.CreatedBy) + topicItem.Avatar
}
} else {
topicItem.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(topicItem.CreatedBy),1)
}
topicItem.LastReplyAt, err = relative_time(topicItem.LastReplyAt)
if err != nil {
InternalError(err,w,r)
@ -297,14 +319,37 @@ func route_forum(w http.ResponseWriter, r *http.Request, user User, sfid string)
if hooks["forum_trow_assign"] != nil {
run_vhook("forum_trow_assign", &topicItem, &forum)
}
topicList = append(topicList, topicItem)
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
}
err = rows.Err()
if err != nil {
InternalError(err,w,r)
return
}
rows.Close()
// Convert the user ID map to a slice, then bulk load the users
var idSlice []int = make([]int,len(reqUserList))
var i int
for userID, _ := range reqUserList {
idSlice[i] = userID
i++
}
// TO-DO: What if a user is deleted via the Control Panel?
userList, err := users.BulkCascadeGetMap(idSlice)
if err != nil {
InternalError(err,w,r)
return
}
// Second pass to the add the user data
// TO-DO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pi := ForumPage{forum.Name,user,headerVars,topicList,*forum,page,last_page,extData}
if pre_render_hooks["pre_render_view_forum"] != nil {
@ -482,6 +527,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r)
return
}
defer rows.Close()
replyItem := Reply{ClassName:""}
for rows.Next() {
@ -561,7 +607,6 @@ func route_topic_id(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r)
return
}
rows.Close()
tpage := TopicPage{topic.Title,user,headerVars,replyList,topic,page,last_page,extData}
if pre_render_hooks["pre_render_view_topic"] != nil {
@ -802,7 +847,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
}
wcount := word_count(content)
res, err := create_topic_stmt.Exec(fid,topic_name,content,parse_message(content),ipaddress,wcount,user.ID)
res, err := create_topic_stmt.Exec(fid,topic_name,content,parse_message(content),user.ID,ipaddress,wcount,user.ID)
if err != nil {
InternalError(err,w,r)
return
@ -882,7 +927,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
return
}
_, err = add_replies_to_topic_stmt.Exec(1,tid)
_, err = add_replies_to_topic_stmt.Exec(1,user.ID,tid)
if err != nil {
InternalError(err,w,r)
return
@ -1922,6 +1967,7 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
InternalErrorJS(err,w,r)
return
}
defer rows.Close()
for rows.Next() {
err = rows.Scan(&asid,&actor_id,&targetUser_id,&event,&elementType,&elementID)
@ -1942,7 +1988,6 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
InternalErrorJS(err,w,r)
return
}
rows.Close()
if len(msglist) != 0 {
msglist = msglist[0:len(msglist)-1]

View File

@ -111,9 +111,9 @@ w.Write(forum_19)
}
}
w.Write(forum_20)
if item.Avatar != "" {
if item.Creator.Avatar != "" {
w.Write(forum_21)
w.Write([]byte(item.Avatar))
w.Write([]byte(item.Creator.Avatar))
w.Write(forum_22)
}
w.Write(forum_23)
@ -125,25 +125,40 @@ w.Write([]byte(item.Link))
w.Write(forum_26)
w.Write([]byte(item.Title))
w.Write(forum_27)
w.Write([]byte(item.UserLink))
w.Write([]byte(item.Creator.Link))
w.Write(forum_28)
w.Write([]byte(item.CreatedByName))
w.Write([]byte(item.Creator.Name))
w.Write(forum_29)
if item.Is_Closed {
w.Write(forum_30)
}
if item.Sticky {
w.Write(forum_31)
}
} else {
w.Write(forum_32)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
if item.LastUser.Avatar != "" {
w.Write(forum_33)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_34)
}
w.Write(forum_35)
}
w.Write([]byte(item.LastUser.Link))
w.Write(forum_36)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_37)
w.Write([]byte(item.LastReplyAt))
w.Write(forum_38)
}
} else {
w.Write(forum_39)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_40)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_41)
}
w.Write(forum_42)
}
w.Write(forum_43)
w.Write(footer_0)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)

View File

@ -77,7 +77,7 @@ var header_15 []byte = []byte(`</div>`)
var topic_0 []byte = []byte(`
<form id="edit_topic_form" action='/topic/edit/submit/`)
var topic_1 []byte = []byte(`' method="post" />
var topic_1 []byte = []byte(`' method="post"></form>
`)
var topic_2 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" href="/topic/`)
var topic_3 []byte = []byte(`?page=`)
@ -558,14 +558,14 @@ var topics_0 []byte = []byte(`
</div>
<div id="topic_list" class="rowblock topic_list">
`)
var topics_1 []byte = []byte(`<div class="rowitem passive datarow `)
var topics_1 []byte = []byte(`<div class="rowitem topic_left passive datarow `)
var topics_2 []byte = []byte(`topic_sticky`)
var topics_3 []byte = []byte(`topic_closed`)
var topics_4 []byte = []byte(`" style="`)
var topics_5 []byte = []byte(`background-image: url(`)
var topics_6 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var topics_7 []byte = []byte(`">
<span class="rowsmall" style="float: right;">
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var topics_8 []byte = []byte(` replies</span><br />
<span class="lastReplyAt">`)
@ -583,15 +583,28 @@ var topics_15 []byte = []byte(`
var topics_16 []byte = []byte(`">Starter: `)
var topics_17 []byte = []byte(`</a>
`)
var topics_18 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E`)
var topics_19 []byte = []byte(`</span>
var topics_18 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_19 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_20 []byte = []byte(`
</span>
</div>
<div class="rowitem topic_right passive datarow" style="`)
var topics_21 []byte = []byte(`background-image: url(`)
var topics_22 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var topics_23 []byte = []byte(`">
<span>
<a href="`)
var topics_24 []byte = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_25 []byte = []byte(`</a><br>
<span class="rowsmall lastReplyAt">Last: `)
var topics_26 []byte = []byte(`</span>
</span>
</div>
`)
var topics_20 []byte = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_21 []byte = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_22 []byte = []byte(`</div>`)
var topics_23 []byte = []byte(`
var topics_27 []byte = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_28 []byte = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_29 []byte = []byte(`</div>`)
var topics_30 []byte = []byte(`
</div>
`)
var forum_0 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" href="/forum/`)
@ -624,14 +637,14 @@ var forum_16 []byte = []byte(`
</div>
<div id="forum_topic_list" class="rowblock topic_list">
`)
var forum_17 []byte = []byte(`<div class="rowitem passive datarow `)
var forum_17 []byte = []byte(`<div class="rowitem topic_left passive datarow `)
var forum_18 []byte = []byte(`topic_sticky`)
var forum_19 []byte = []byte(`topic_closed`)
var forum_20 []byte = []byte(`" style="`)
var forum_21 []byte = []byte(`background-image: url(`)
var forum_22 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var forum_23 []byte = []byte(`">
<span class="rowsmall" style="float: right;">
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var forum_24 []byte = []byte(` replies</span><br />
<span class="lastReplyAt">`)
@ -645,15 +658,28 @@ var forum_27 []byte = []byte(`</a>
var forum_28 []byte = []byte(`">Starter: `)
var forum_29 []byte = []byte(`</a>
`)
var forum_30 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E`)
var forum_31 []byte = []byte(`</span>
var forum_30 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_31 []byte = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_32 []byte = []byte(`
</span>
</div>
<div class="rowitem topic_right passive datarow" style="`)
var forum_33 []byte = []byte(`background-image: url(`)
var forum_34 []byte = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
var forum_35 []byte = []byte(`">
<span>
<a href="`)
var forum_36 []byte = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_37 []byte = []byte(`</a><br>
<span class="rowsmall lastReplyAt">Last: `)
var forum_38 []byte = []byte(`</span>
</span>
</div>
`)
var forum_32 []byte = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_33 []byte = []byte(` <a href="/topics/create/`)
var forum_34 []byte = []byte(`">Start one?</a>`)
var forum_35 []byte = []byte(`</div>`)
var forum_36 []byte = []byte(`
var forum_39 []byte = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_40 []byte = []byte(` <a href="/topics/create/`)
var forum_41 []byte = []byte(`">Start one?</a>`)
var forum_42 []byte = []byte(`</div>`)
var forum_43 []byte = []byte(`
</div>
`)

View File

@ -76,9 +76,9 @@ w.Write(topics_3)
}
}
w.Write(topics_4)
if item.Avatar != "" {
if item.Creator.Avatar != "" {
w.Write(topics_5)
w.Write([]byte(item.Avatar))
w.Write([]byte(item.Creator.Avatar))
w.Write(topics_6)
}
w.Write(topics_7)
@ -98,23 +98,38 @@ w.Write([]byte(item.ForumName))
w.Write(topics_14)
}
w.Write(topics_15)
w.Write([]byte(item.UserLink))
w.Write([]byte(item.Creator.Link))
w.Write(topics_16)
w.Write([]byte(item.CreatedByName))
w.Write([]byte(item.Creator.Name))
w.Write(topics_17)
if item.Is_Closed {
w.Write(topics_18)
}
if item.Sticky {
w.Write(topics_19)
}
} else {
w.Write(topics_20)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
if item.LastUser.Avatar != "" {
w.Write(topics_21)
}
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_22)
}
w.Write(topics_23)
w.Write([]byte(item.LastUser.Link))
w.Write(topics_24)
w.Write([]byte(item.LastUser.Name))
w.Write(topics_25)
w.Write([]byte(item.LastReplyAt))
w.Write(topics_26)
}
} else {
w.Write(topics_27)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_28)
}
w.Write(topics_29)
}
w.Write(topics_30)
w.Write(footer_0)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)

View File

@ -13,6 +13,7 @@ import (
"text/template/parse"
)
// TO-DO: Turn this file into a library
var ctemplates []string
var tmpl_ptr_map map[string]interface{} = make(map[string]interface{})
var text_overlap_list map[string]int
@ -58,6 +59,10 @@ type CTemplateSet struct
}
func (c *CTemplateSet) compile_template(name string, dir string, expects string, expectsInt interface{}, varList map[string]VarItem) (out string) {
if dev.DebugMode {
fmt.Println("Compiling template '" + name + "'")
}
c.dir = dir
c.doImports = true
c.funcMap = map[string]interface{}{
@ -354,9 +359,14 @@ func (c *CTemplateSet) compile_subswitch(varholder string, holdreflect reflect.V
}
}
if !cur.IsValid() {
panic(varholder + varbit + "^\n" + "Invalid value. Maybe, it doesn't exist?")
}
cur = cur.FieldByName(id)
if cur.Kind() == reflect.Interface {
cur = cur.Elem()
// TO-DO: Surely, there's a better way of detecting this?
/*if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
varbit = "string(" + varbit + "." + id + ")"*/
//if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
@ -800,6 +810,21 @@ func (c *CTemplateSet) compile_if_varsub(varname string, varholder string, templ
continue
}
// TO-DO: Fix this up so that it works for regular pointers and not just struct pointers. Ditto for the other cur.Kind() == reflect.Ptr we have in this file
if cur.Kind() == reflect.Ptr {
if dev.SuperDebug {
fmt.Println("Looping over pointer")
}
for cur.Kind() == reflect.Ptr {
cur = cur.Elem()
}
if dev.SuperDebug {
fmt.Println("Data Kind:",cur.Kind().String())
fmt.Println("Field Bit:",bit)
}
}
cur = cur.FieldByName(bit)
if cur.Kind() == reflect.Interface {
cur = cur.Elem()

View File

@ -15,15 +15,23 @@
{{end}}
</div>
<div id="forum_topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="rowsmall" style="float: right;">
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span>
</span>
<span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.UserLink}}">Starter: {{.CreatedByName}}</a>
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TO-DO: Avoid the double '|' when both .Is_Closed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span>
</div>
<div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>
</span>
</div>
{{else}}<div class="rowitem passive">There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}

View File

@ -1,6 +1,9 @@
{{template "header.html" . }}
{{template "panel-menu.html" . }}
<div class="colstack_right">
<div id="panel_dashboard_right" class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Dashboard</a></div>
</div>
<div id="panel_dashboard" class="colstack_grid">
{{range .GridItems}}
<div id="{{.ID}}" class="grid_item {{.Class}}" title="{{.Note}}" style="{{if .TextColour}}color: {{.TextColour}};{{end}}

View File

@ -19,17 +19,23 @@
<div style="clear: both;"></div>
</div>
<div id="forum_topic_list" class="rowblock topic_list" style="position: relative;z-index: 50;">
{{range .ItemList}}<div class="rowitem passive datarow" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .Is_Closed}}background-color: #eaeaea;{{end}}">
<span class="rowsmall" style="float: right;">
{{range .ItemList}}<div class="rowitem topic_left passive datarow" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}{{if .Sticky}}background-color: #FFFFCC;{{else if .Is_Closed}}background-color: #eaeaea;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span>
</span>
<span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.UserLink}}">Starter: {{.CreatedByName}}</a>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span>
</span>
</div>
<div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>
</span>
</div>
{{else}}<div class="rowitem passive">There aren't any topics in here yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}
</div>
{{template "footer.html" . }}

View File

@ -1,6 +1,6 @@
{{template "header.html" . }}
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}' method="post" />
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}' method="post"></form>
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/topic/{{.Topic.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}" />

View File

@ -3,15 +3,23 @@
<div class="rowitem"><a>Topic List</a></div>
</div>
<div id="topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Avatar}}background-image: url({{.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="rowsmall" style="float: right;">
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="lastReplyAt">{{.LastReplyAt}}</span>
</span>
<span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<br /><a class="rowsmall" href="{{.UserLink}}">Starter: {{.CreatedByName}}</a>
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E{{end}}</span>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TO-DO: Avoid the double '|' when both .Is_Closed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .Is_Closed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span>
</div>
<div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
<span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>
</span>
</div>
{{else}}<div class="rowitem passive">There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">Start one?</a>{{end}}</div>{{end}}

View File

@ -251,6 +251,7 @@ a {
textarea {
background-color: #444444;
border-color: #555555;
color: #999999;
width: calc(100% - 15px);
min-height: 80px;
}
@ -412,6 +413,23 @@ input, select, textarea {
content: "Locked";
}
/* Temporary hack, so that I don't break the topic lists of the other themes */
.topic_list .topic_inner_right {
display: none;
}
.topic_list .rowitem {
float: left;
overflow: hidden;
}
.topic_list .topic_left {
width: calc(100% - 284px);
}
.topic_list .topic_right {
height: 35px;
margin-left: 8px;
width: 108px;
}
/* Profiles */
#profile_left_lane {
width: 220px;
@ -453,6 +471,32 @@ input, select, textarea {
padding-left: 136px;
}
.colstack_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin-top: 3px;
grid-gap: 3px;
text-align: center;
}
.grid_stat, .grid_istat {
padding-top: 10px;
padding-bottom: 10px;
font-size: 13px;
background-color: rgb(61,61,61);
}
#panel_dashboard_right .colstack_head .rowitem {
padding: 10px;
}
#panel_dashboard_right .colstack_head a {
text-align: center;
width: 100%;
display: block;
font-size: 15px;
}
@media(max-width: 935px) {
.simple .user_tag {
display: none;
@ -555,6 +599,21 @@ input, select, textarea {
}
}
@media(max-width: 500px) {
.topic_list .topic_inner_right {
display: block;
}
.topic_list .rowitem {
float: none;
}
.topic_list .topic_left {
width: calc(100% - 84px);
}
.topic_list .topic_right {
display: none;
}
}
@media(max-width: 470px) {
.menu_create_topic, .like_count_label, .like_count {
display: none;

View File

@ -68,24 +68,19 @@ type TopicsRow struct
Sticky bool
CreatedAt string
LastReplyAt string
//LastReplyBy int
LastReplyBy int
ParentID int
Status string // Deprecated. Marked for removal. -Is there anything we could use it for?
IpAddress string
PostCount int
LikeCount int
ClassName string
Data string // Used for report metadata
UserLink string
CreatedByName string
Avatar string
Creator *User
Css template.CSS
ContentLines int
Tag string
URL string
URLPrefix string
URLName string
Level int
LastUser *User
ForumName string //TopicsRow
ForumLink string
@ -99,7 +94,6 @@ func get_topicuser(tid int) (TopicUser,error) {
if err != nil {
return TopicUser{ID:tid}, err
}
init_user_perms(user)
// We might be better off just passing seperate topic and user structs to the caller?
return copy_topic_to_topicuser(topic, user), nil
@ -112,9 +106,7 @@ func get_topicuser(tid int) (TopicUser,error) {
if err != nil {
return TopicUser{ID:tid}, err
}
init_user_perms(user)
tu := copy_topic_to_topicuser(topic, user)
return tu, nil
return copy_topic_to_topicuser(topic, user), nil
}
}

36
user.go
View File

@ -233,6 +233,16 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
}
}
pusher, ok := w.(http.Pusher)
if ok {
pusher.Push("/static/main.css", nil)
pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files
// TO-DO: Push the theme scripts
// TO-DO: Push avatars?
}
return headerVars, true
}
func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (success bool) {
@ -266,6 +276,16 @@ func _session_check(w http.ResponseWriter, r *http.Request, user *User) (headerV
}
}
pusher, ok := w.(http.Pusher)
if ok {
pusher.Push("/static/main.css", nil)
pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files
// TO-DO: Push the theme scripts
// TO-DO: Push avatars?
}
return headerVars, true
}
@ -278,14 +298,6 @@ func _pre_route(w http.ResponseWriter, r *http.Request) (User,bool) {
return *user, true
}
if user.Is_Super_Admin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = groups[user.Group].Perms
user.PluginPerms = groups[user.Group].PluginPerms
}
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
PreError("Bad IP",w,r)
@ -394,6 +406,14 @@ func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error
}
func init_user_perms(user *User) {
if user.Is_Super_Admin {
user.Perms = AllPerms
user.PluginPerms = AllPluginPerms
} else {
user.Perms = groups[user.Group].Perms
user.PluginPerms = groups[user.Group].PluginPerms
}
user.Is_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin
user.Is_Super_Mod = user.Is_Admin || groups[user.Group].Is_Mod
user.Is_Mod = user.Is_Super_Mod

View File

@ -1,14 +1,17 @@
package main
import "log"
import "sync"
import "errors"
import "strings"
import "strconv"
import "database/sql"
import (
"fmt"
"log"
"sync"
"errors"
"strings"
"strconv"
"database/sql"
import "./query_gen/lib"
import "golang.org/x/crypto/bcrypt"
"./query_gen/lib"
"golang.org/x/crypto/bcrypt"
)
// TO-DO: Add the watchdog goroutine
var users UserStore
@ -19,6 +22,8 @@ type UserStore interface {
Get(id int) (*User, error)
GetUnsafe(id int) (*User, error)
CascadeGet(id int) (*User, error)
//BulkCascadeGet(ids []int) ([]*User, error)
BulkCascadeGetMap(ids []int) (map[int]*User, error)
BypassGet(id int) (*User, error)
Set(item *User) error
Add(item *User) error
@ -112,6 +117,114 @@ func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
return user, err
}
// WARNING: We did a little hack to make this as thin and quick as possible to reduce lock contention, use the * Cascade* methods instead for normal use
func (sus *MemoryUserStore) bulkGet(ids []int) (list []*User) {
list = make([]*User,len(ids))
sus.RLock()
for i, id := range ids {
list[i] = sus.items[id]
}
sus.RUnlock()
return list
}
// TO-DO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TO-DO: ID of 0 should always error?
func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) {
var id_count int = len(ids)
list = make(map[int]*User)
if id_count == 0 {
return list, nil
}
var still_here []int
slice_list := sus.bulkGet(ids)
for i, slice_item := range slice_list {
if slice_item != nil {
list[slice_item.ID] = slice_item
} else {
still_here = append(still_here,ids[i])
}
}
ids = still_here
// If every user is in the cache, then return immediately
if len(ids) == 0 {
return list, nil
}
var qlist string
var uidList []interface{}
for _, id := range ids {
uidList = append(uidList,strconv.Itoa(id))
qlist += "?,"
}
qlist = qlist[0:len(qlist) - 1]
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid IN("+qlist+")","","")
if err != nil {
return nil, err
}
rows, err := stmt.Query(uidList...)
if err != nil {
return nil, err
}
for rows.Next() {
user := &User{Loggedin:true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
if err != nil {
return nil, err
}
// Initialise the user
if user.Avatar != "" {
if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
}
} else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1)
}
user.Link = build_profile_url(name_to_slug(user.Name),user.ID)
user.Tag = groups[user.Group].Tag
init_user_perms(user)
// Add it to the cache...
sus.Set(user)
// Add it to the list to be returned
list[user.ID] = user
}
// Did we miss any users?
if id_count > len(list) {
var sid_list string
for _, id := range ids {
_, ok := list[id]
if !ok {
sid_list += strconv.Itoa(id) + ","
}
}
// We probably don't need this, but it might be useful in case of bugs in BulkCascadeGetMap
if sid_list == "" {
if dev.DebugMode {
fmt.Println("This data is sampled later in the BulkCascadeGetMap function, so it might miss the cached IDs")
fmt.Println("id_count",id_count)
fmt.Println("ids",ids)
fmt.Println("list",list)
}
return list, errors.New("We weren't able to find a user, but we don't know which one")
}
sid_list = sid_list[0:len(sid_list) - 1]
return list, errors.New("Unable to find the users with the following IDs: " + sid_list)
}
return list, nil
}
func (sus *MemoryUserStore) BypassGet(id int) (*User, error) {
user := &User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
@ -322,6 +435,53 @@ func (sus *SqlUserStore) CascadeGet(id int) (*User, error) {
return &user, err
}
// TO-DO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err error) {
var qlist string
var uidList []interface{}
for _, id := range ids {
uidList = append(uidList,strconv.Itoa(id))
qlist += "?,"
}
qlist = qlist[0:len(qlist) - 1]
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid IN("+qlist+")","","")
if err != nil {
return nil, err
}
rows, err := stmt.Query(uidList...)
if err != nil {
return nil, err
}
list = make(map[int]*User)
for rows.Next() {
user := &User{Loggedin:true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
if err != nil {
return nil, err
}
// Initialise the user
if user.Avatar != "" {
if user.Avatar[0] == '.' {
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + user.Avatar
}
} else {
user.Avatar = strings.Replace(config.Noavatar,"{id}",strconv.Itoa(user.ID),1)
}
user.Link = build_profile_url(name_to_slug(user.Name),user.ID)
user.Tag = groups[user.Group].Tag
init_user_perms(user)
// Add it to the list to be returned
list[user.ID] = user
}
return list, nil
}
func (sus *SqlUserStore) BypassGet(id int) (*User, error) {
user := User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)