From f41c281e438181f0395c38c9e1992df82982c01a Mon Sep 17 00:00:00 2001 From: Azareal Date: Sun, 6 Aug 2017 16:22:18 +0100 Subject: [PATCH] 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
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. --- .gitignore | 2 + extend.go | 3 +- gen_mysql.go | 14 +- gen_pgsql.go | 4 +- gen_router.go | 22 +++- general_test.go | 85 ++++++------ install/install.go | 4 +- main.go | 19 ++- misc_test.go | 37 +++++- mysql.sql | 6 +- pages.go | 4 +- panel_routes.go | 1 + plugin_socialgroups.go | 2 +- plugin_test.go | 13 +- public/global.js | 5 +- query_gen/lib/pgsql.go | 13 +- query_gen/lib/querygen.go | 12 +- query_gen/main.go | 8 +- router_gen/main.go | 22 +++- routes.go | 117 +++++++++++----- template_forum.go | 31 +++-- template_list.go | 62 ++++++--- template_topics.go | 29 +++- templates.go | 25 ++++ templates/forum.html | 16 ++- templates/panel-dashboard.html | 5 +- templates/socialgroups_view_group.html | 12 +- templates/topic.html | 2 +- templates/topics.html | 16 ++- themes/shadow/public/main.css | 59 +++++++++ topic.go | 18 +-- user.go | 36 +++-- user_store.go | 176 +++++++++++++++++++++++-- 33 files changed, 679 insertions(+), 201 deletions(-) diff --git a/.gitignore b/.gitignore index 008366cd..19cd88ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ tmp/* tmp2/* +cert_test/* + uploads/avatar_* uploads/socialgroup_* bin/* diff --git a/extend.go b/extend.go index f3e9ae38..7acb8237 100644 --- a/extend.go +++ b/extend.go @@ -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, } diff --git a/gen_mysql.go b/gen_mysql.go index 5e5f1131..f15a9931 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -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 } diff --git a/gen_pgsql.go b/gen_pgsql.go index 0f466ca6..64e9e07e 100644 --- a/gen_pgsql.go +++ b/gen_pgsql.go @@ -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 } diff --git a/gen_router.go b/gen_router.go index 3389cde1..d0f85e94 100644 --- a/gen_router.go +++ b/gen_router.go @@ -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) diff --git a/general_test.go b/general_test.go index 9a0c646a..0f8dcd38 100644 --- a/general_test.go +++ b/general_test.go @@ -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) { diff --git a/install/install.go b/install/install.go index 363fb9a2..a0ee4972 100644 --- a/install/install.go +++ b/install/install.go @@ -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 = "" diff --git a/main.go b/main.go index 57254cb9..bca4ae57 100644 --- a/main.go +++ b/main.go @@ -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 == "" { diff --git a/misc_test.go b/misc_test.go index 6abbbf4d..af13ba8a 100644 --- a/misc_test.go +++ b/misc_test.go @@ -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.") } } diff --git a/mysql.sql b/mysql.sql index 047ff7aa..dcff8cf3 100644 --- a/mysql.sql +++ b/mysql.sql @@ -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); diff --git a/pages.go b/pages.go index 9d08732f..71cc7772 100644 --- a/pages.go +++ b/pages.go @@ -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 diff --git a/panel_routes.go b/panel_routes.go index 66060c09..6233f9f1 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -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) diff --git a/plugin_socialgroups.go b/plugin_socialgroups.go index dee9f633..774addd3 100644 --- a/plugin_socialgroups.go +++ b/plugin_socialgroups.go @@ -55,7 +55,7 @@ type SocialGroupPage struct Title string CurrentUser User Header HeaderVars - ItemList []TopicUser + ItemList []*TopicsRow Forum Forum SocialGroup SocialGroup Page int diff --git a/plugin_test.go b/plugin_test.go index aa92896a..c19e5323 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -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]","hi") msgList = addMEPair(msgList,"[s]hi[/s]","hi") 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]","hi") + 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]","hi") + } 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") diff --git a/public/global.js b/public/global.js index 2ccba975..524d6851 100644 --- a/public/global.js +++ b/public/global.js @@ -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'); } diff --git a/query_gen/lib/pgsql.go b/query_gen/lib/pgsql.go index 71c2d6b5..aa7ab457 100644 --- a/query_gen/lib/pgsql.go +++ b/query_gen/lib/pgsql.go @@ -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 + "" diff --git a/query_gen/lib/querygen.go b/query_gen/lib/querygen.go index 1dac170e..5ea55d23 100644 --- a/query_gen/lib/querygen.go +++ b/query_gen/lib/querygen.go @@ -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) diff --git a/query_gen/main.go b/query_gen/main.go index 6ac7b1f6..c71bdea9 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -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 = ?") diff --git a/router_gen/main.go b/router_gen/main.go index 9c6f126d..58bb9ffc 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -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) diff --git a/routes.go b/routes.go index 0ee099d3..2e387d3d 100644 --- a/routes.go +++ b/routes.go @@ -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] diff --git a/template_forum.go b/template_forum.go index 4e457433..a24ce4fb 100644 --- a/template_forum.go +++ b/template_forum.go @@ -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) diff --git a/template_list.go b/template_list.go index 80cddd69..76241590 100644 --- a/template_list.go +++ b/template_list.go @@ -77,7 +77,7 @@ var header_15 []byte = []byte(``) var topic_0 []byte = []byte(` +var topic_1 []byte = []byte(`' method="post">
`) var topic_2 []byte = []byte(`
`) -var topics_1 []byte = []byte(`
- + `) var topics_8 []byte = []byte(` replies
`) @@ -583,15 +583,28 @@ var topics_15 []byte = []byte(` var topics_16 []byte = []byte(`">Starter: `) var topics_17 []byte = []byte(`
`) -var topics_18 []byte = []byte(` | 🔒︎`) -var topics_19 []byte = []byte(` +var topics_18 []byte = []byte(` | 🔒︎`) +var topics_19 []byte = []byte(` | 📍︎`) +var topics_20 []byte = []byte(` + +
+
+ + `) +var topics_25 []byte = []byte(`
+ Last: `) +var topics_26 []byte = []byte(`
`) -var topics_20 []byte = []byte(`
There aren't any topics yet.`) -var topics_21 []byte = []byte(` Start one?`) -var topics_22 []byte = []byte(`
`) -var topics_23 []byte = []byte(` +var topics_27 []byte = []byte(`
There aren't any topics yet.`) +var topics_28 []byte = []byte(` Start one?`) +var topics_29 []byte = []byte(`
`) +var topics_30 []byte = []byte(`
`) var forum_0 []byte = []byte(`
`) -var forum_17 []byte = []byte(`
- + `) var forum_24 []byte = []byte(` replies
`) @@ -645,15 +658,28 @@ var forum_27 []byte = []byte(`
var forum_28 []byte = []byte(`">Starter: `) var forum_29 []byte = []byte(` `) -var forum_30 []byte = []byte(` | 🔒︎`) -var forum_31 []byte = []byte(` +var forum_30 []byte = []byte(` | 🔒︎`) +var forum_31 []byte = []byte(` | 📍︎`) +var forum_32 []byte = []byte(` + +
+
+ + `) +var forum_37 []byte = []byte(`
+ Last: `) +var forum_38 []byte = []byte(`
`) -var forum_32 []byte = []byte(`
There aren't any topics in this forum yet.`) -var forum_33 []byte = []byte(` Start one?`) -var forum_35 []byte = []byte(`
`) -var forum_36 []byte = []byte(` +var forum_39 []byte = []byte(`
There aren't any topics in this forum yet.`) +var forum_40 []byte = []byte(` Start one?`) +var forum_42 []byte = []byte(`
`) +var forum_43 []byte = []byte(`
`) diff --git a/template_topics.go b/template_topics.go index 211401af..eaa49003 100644 --- a/template_topics.go +++ b/template_topics.go @@ -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) diff --git a/templates.go b/templates.go index 4bdf5a21..403e848b 100644 --- a/templates.go +++ b/templates.go @@ -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() diff --git a/templates/forum.html b/templates/forum.html index 9c798209..5af2066c 100644 --- a/templates/forum.html +++ b/templates/forum.html @@ -15,15 +15,23 @@ {{end}}
- {{range .ItemList}}
- + {{range .ItemList}}
+ {{.PostCount}} replies
{{.LastReplyAt}}
{{.Title}} -
Starter: {{.CreatedByName}} - {{if .Is_Closed}} | 🔒︎{{end}} +
Starter: {{.Creator.Name}} + {{/** 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}} | 🔒︎{{end}} + {{if .Sticky}} | 📍︎{{end}} +
+
+
+ + {{.LastUser.Name}}
+ Last: {{.LastReplyAt}}
{{else}}
There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} Start one?{{end}}
{{end}} diff --git a/templates/panel-dashboard.html b/templates/panel-dashboard.html index 6d581b5c..60842ab4 100644 --- a/templates/panel-dashboard.html +++ b/templates/panel-dashboard.html @@ -1,6 +1,9 @@ {{template "header.html" . }} {{template "panel-menu.html" . }} -
+
+
+ +
{{range .GridItems}}
- {{range .ItemList}}
- + {{range .ItemList}}
+ {{.PostCount}} replies
{{.LastReplyAt}}
{{.Title}} -
Starter: {{.CreatedByName}} +
Starter: {{.Creator.Name}} {{if .Is_Closed}} | 🔒︎{{end}}
+
+ + {{.LastUser.Name}}
+ Last: {{.LastReplyAt}} +
+
{{else}}
There aren't any topics in here yet.{{if .CurrentUser.Perms.CreateTopic}} Start one?{{end}}
{{end}}
{{template "footer.html" . }} diff --git a/templates/topic.html b/templates/topic.html index 21e1a636..279f30c1 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -1,6 +1,6 @@ {{template "header.html" . }} -
+
{{if gt .Page 1}}{{end}} {{if ne .LastPage .Page}} diff --git a/templates/topics.html b/templates/topics.html index 28592ff7..9e920c9d 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -3,15 +3,23 @@
- {{range .ItemList}}
- + {{range .ItemList}}
+ {{.PostCount}} replies
{{.LastReplyAt}}
{{.Title}} {{if .ForumName}}{{.ForumName}}{{end}} -
Starter: {{.CreatedByName}} - {{if .Is_Closed}} | 🔒︎{{end}} +
Starter: {{.Creator.Name}} + {{/** 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}} | 🔒︎{{end}} + {{if .Sticky}} | 📍︎{{end}} +
+
+
+ + {{.LastUser.Name}}
+ Last: {{.LastReplyAt}}
{{else}}
There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} Start one?{{end}}
{{end}} diff --git a/themes/shadow/public/main.css b/themes/shadow/public/main.css index 1e1e8b3b..477bce29 100644 --- a/themes/shadow/public/main.css +++ b/themes/shadow/public/main.css @@ -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; diff --git a/topic.go b/topic.go index a8dac952..66ffda80 100644 --- a/topic.go +++ b/topic.go @@ -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 } } diff --git a/user.go b/user.go index d403cbac..f8cfdf10 100644 --- a/user.go +++ b/user.go @@ -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 diff --git a/user_store.go b/user_store.go index 65905f17..ab55d1e7 100644 --- a/user_store.go +++ b/user_store.go @@ -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)