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)