Added support for HTTP/2 Push.

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

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

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

2
.gitignore vendored
View File

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

View File

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

View File

@ -39,8 +39,8 @@ var get_topic_basic_stmt *sql.Stmt
var get_activity_entry_stmt *sql.Stmt var get_activity_entry_stmt *sql.Stmt
var forum_entry_exists_stmt *sql.Stmt var forum_entry_exists_stmt *sql.Stmt
var group_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_forum_topics_offset_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt
var get_topic_list_stmt *sql.Stmt var get_topic_list_stmt *sql.Stmt
var get_topic_user_stmt *sql.Stmt var get_topic_user_stmt *sql.Stmt
var get_topic_by_reply_stmt *sql.Stmt var get_topic_by_reply_stmt *sql.Stmt
@ -310,14 +310,14 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing get_topic_replies_offset statement.") log.Print("Preparing get_forum_topics_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 ?,?") 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 { if err != nil {
return err return err
} }
log.Print("Preparing get_forum_topics_offset statement.") log.Print("Preparing get_topic_replies_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 ?,?") 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 { if err != nil {
return err return err
} }
@ -365,7 +365,7 @@ func _gen_mysql() (err error) {
} }
log.Print("Preparing create_topic statement.") 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 { if err != nil {
return err return err
} }
@ -473,7 +473,7 @@ func _gen_mysql() (err error) {
} }
log.Print("Preparing add_replies_to_topic statement.") 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 { if err != nil {
return err return err
} }

View File

@ -50,7 +50,7 @@ func _gen_pgsql() (err error) {
} }
log.Print("Preparing add_replies_to_topic statement.") 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 { if err != nil {
return err return err
} }
@ -74,7 +74,7 @@ func _gen_pgsql() (err error) {
} }
log.Print("Preparing update_forum_cache statement.") 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 { if err != nil {
return err return err
} }

View File

@ -62,9 +62,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = 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) if dev.SuperDebug {
//fmt.Println("extra_data:",extra_data) 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" { if prefix == "/static" {
req.URL.Path += extra_data req.URL.Path += extra_data
@ -207,6 +211,18 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.UploadHandler(w,req) router.UploadHandler(w,req)
return return
case "": 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) config.DefaultRoute(w,req,user)
return return
//default: NotFound(w,req) //default: NotFound(w,req)

View File

@ -57,16 +57,16 @@ func gloinit() {
} }
if config.CacheTopicUser == CACHE_STATIC { if config.CacheTopicUser == CACHE_STATIC {
users = NewMemoryUserStore(user_cache_capacity) users = NewMemoryUserStore(config.UserCacheCapacity)
topics = NewMemoryTopicStore(topic_cache_capacity) topics = NewMemoryTopicStore(config.TopicCacheCapacity)
} else { } else {
users = NewSqlUserStore() users = NewSqlUserStore()
topics = NewSqlTopicStore() topics = NewSqlTopicStore()
} }
init_static_files() init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
//log.SetOutput(os.Stdout) //log.SetOutput(os.Stdout)
auth = NewDefaultAuth()
router = NewGenRouter(http.FileServer(http.Dir("./uploads"))) router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
gloinited = true 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"} 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"} 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 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,"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,"",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,"","",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,"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,"",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,"","",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,"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,"",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,"","",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,"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,"",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,"","",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,"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,"",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,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
headerVars := HeaderVars{ headerVars := HeaderVars{
NoticeList:[]string{"test"}, NoticeList:[]string{"test"},
@ -103,6 +103,7 @@ func BenchmarkTopicTemplateSerial(b *testing.B) {
Widgets:PageWidgets{ Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"), LeftSidebar: template.HTML("lalala"),
}, },
Site:site,
} }
tpage := TopicPage{"Topic Blah",user,headerVars,replyList,topic,1,1,extData} 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) { func BenchmarkTopicsTemplateSerial(b *testing.B) {
b.ReportAllocs() 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"} 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,"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"} 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 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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,Creator:&admin,LastUser:&user,ClassName:"", IpAddress: "127.0.0.1"})
headerVars := HeaderVars{ headerVars := HeaderVars{
NoticeList:[]string{"test"}, NoticeList:[]string{"test"},
@ -189,6 +190,7 @@ func BenchmarkTopicsTemplateSerial(b *testing.B) {
Widgets:PageWidgets{ Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"), LeftSidebar: template.HTML("lalala"),
}, },
Site:site,
} }
w := ioutil.Discard 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) { func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
@ -253,10 +254,10 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
admin, err := users.CascadeGet(1) admin, err := users.CascadeGet(1)
if err != nil { if err != nil {
panic(err) b.Fatal(err)
} }
if !admin.Is_Admin { 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_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year}
admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,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 := topic_req
topic_req_admin.AddCookie(&admin_uid_cookie) topic_req_admin.AddCookie(&admin_uid_cookie)
topic_req_admin.AddCookie(&admin_session_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() { for pb.Next() {
topic_w.Body.Reset() 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) { b.RunParallel(func(pb *testing.PB) {
topic_w := httptest.NewRecorder() topic_w := httptest.NewRecorder()
topic_req := httptest.NewRequest("get","/topic/1",bytes.NewReader(nil)) topic_req := httptest.NewRequest("get","/topic/1",bytes.NewReader(nil))
topic_handler := http.HandlerFunc(route_topic_id)
for pb.Next() { for pb.Next() {
topic_w.Body.Reset() 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) { func BenchmarkForumsAdminRouteParallel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
if !gloinited { if !gloinited {
@ -619,7 +626,7 @@ func BenchmarkQueryTopicParallel(b *testing.B) {
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
tu := TopicUser{Css: no_css_tmpl} var tu TopicUser
for pb.Next() { 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) 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 { if err == ErrNoRows {
@ -640,7 +647,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
} }
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
tu := TopicUser{Css: no_css_tmpl} var tu TopicUser
for pb.Next() { 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) 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 { if err == ErrNoRows {
@ -656,7 +663,7 @@ func BenchmarkQueryPreparedTopicParallel(b *testing.B) {
func BenchmarkQueriesSerial(b *testing.B) { func BenchmarkQueriesSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
tu := TopicUser{Css: no_css_tmpl} var tu TopicUser
b.Run("topic", func(b *testing.B) { b.Run("topic", func(b *testing.B) {
for i := 0; i < b.N; i++ { 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) 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 is_super_admin bool
var group int var group int
b.Run("topic_replies_scan", func(b *testing.B) { b.Run("topic_replies_scan", func(b *testing.B) {

View File

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

19
main.go
View File

@ -32,7 +32,9 @@ var startTime time.Time
var templates = template.New("") var templates = template.New("")
//var no_css_tmpl template.CSS = template.CSS("") //var no_css_tmpl template.CSS = template.CSS("")
var settings map[string]interface{} = make(map[string]interface{}) 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 groups []Group
var groupCapCount int var groupCapCount int
var static_files map[string]SFile = make(map[string]SFile) 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 { func compile_templates() error {
var c CTemplateSet 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"} 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{ headerVars := HeaderVars{
Site:site, Site:site,
NoticeList:[]string{"test"}, NoticeList:[]string{"test"},
@ -88,15 +93,15 @@ func compile_templates() error {
forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData} forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData}
forums_tmpl := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList) forums_tmpl := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList)
var topicsList []TopicsRow 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"}) 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_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData}
topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList) topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList)
var topicList []TopicUser //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}) //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_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) forum_tmpl := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList)
log.Print("Writing the templates") log.Print("Writing the templates")
@ -191,7 +196,6 @@ func main(){
} }
init_static_files() init_static_files()
external_sites["YT"] = "https://www.youtube.com/"
log.Print("Initialising the widgets") log.Print("Initialising the widgets")
err = init_widgets() err = init_widgets()
@ -298,6 +302,7 @@ func main(){
// pprof.StopCPUProfile() // pprof.StopCPUProfile()
//} //}
// TO-DO: Let users run *both* HTTP and HTTPS
log.Print("Initialising the HTTP server") log.Print("Initialising the HTTP server")
if !site.EnableSsl { if !site.EnableSsl {
if site.Port == "" { if site.Port == "" {

View File

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

View File

@ -49,7 +49,7 @@ CREATE TABLE `topics`(
`parsed_content` text not null, `parsed_content` text not null,
`createdAt` datetime not null, `createdAt` datetime not null,
`lastReplyAt` datetime not null, `lastReplyAt` datetime not null,
/*`lastReplyBy` int not null,*/ `lastReplyBy` int not null,
`createdBy` int not null, `createdBy` int not null,
`is_closed` tinyint DEFAULT 0 not null, `is_closed` tinyint DEFAULT 0 not null,
`sticky` 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 (4,1,'{}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (5,1,'{}'); INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (5,1,'{}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (6,1,'{}'); INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (6,1,'{}');
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`) 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,2); 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`) INSERT INTO replies(`tid`,`content`,`createdAt`,`createdBy`,`lastEdit`,`lastEditBy`)
VALUES (1,'Reply 1',UTC_TIMESTAMP(),1,0,0); VALUES (1,'Reply 1',UTC_TIMESTAMP(),1,0,0);

View File

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

View File

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

View File

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

View File

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

View File

@ -95,7 +95,10 @@ $(document).ready(function(){
} }
if(window["WebSocket"]) { 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.onopen = function() {
conn.send("page " + document.location.pathname + '\r'); conn.send("page " + document.location.pathname + '\r');
} }

View File

@ -1,4 +1,4 @@
/* WIP Under *Heavy* Construction */ /* WIP Under Really Heavy Construction */
package qgen package qgen
import "strings" import "strings"
@ -144,7 +144,13 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
querystr += "`" + item.Column + "` =" querystr += "`" + item.Column + "` ="
for _, token := range item.Expr { for _, token := range item.Expr {
switch(token.Type) { 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 + "" querystr += " " + token.Contents + ""
case "column": case "column":
querystr += " `" + token.Contents + "`" querystr += " `" + token.Contents + "`"
@ -166,8 +172,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
switch(token.Type) { switch(token.Type) {
case "function": 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: 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 strings.ToUpper(token.Contents) == "UTC_TIMESTAMP()" {
if token.Contents == "UTC_TIMESTAMP()" {
token.Contents = "LOCALTIMESTAMP()" token.Contents = "LOCALTIMESTAMP()"
} }
querystr += " " + token.Contents + "" querystr += " " + token.Contents + ""

View File

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

View File

@ -223,14 +223,14 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("group_entry_exists","users_groups","gid","name = ''","gid ASC","0,1") adapter.SimpleSelect("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 return nil
} }
func write_left_joins(adapter qgen.DB_Adapter) error { 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_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_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 = ?","","") 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 { 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'") 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 { 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 = ?") adapter.SimpleUpdate("remove_replies_from_topic","topics","postCount = postCount - ?","tid = ?")

View File

@ -140,9 +140,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = 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) if dev.SuperDebug {
//fmt.Println("extra_data:",extra_data) 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" { if prefix == "/static" {
req.URL.Path += extra_data req.URL.Path += extra_data
@ -174,6 +178,18 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.UploadHandler(w,req) router.UploadHandler(w,req)
return return
case "": 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) config.DefaultRoute(w,req,user)
return return
//default: NotFound(w,req) //default: NotFound(w,req)

117
routes.go
View File

@ -70,6 +70,18 @@ func route_fstatic(w http.ResponseWriter, r *http.Request){
http.ServeFile(w,r,r.URL.Path) 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){ func route_overview(w http.ResponseWriter, r *http.Request, user User){
headerVars, ok := SessionCheck(w,r,&user) headerVars, ok := SessionCheck(w,r,&user)
if !ok { if !ok {
@ -134,8 +146,9 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User){
} }
qlist = qlist[0:len(qlist) - 1] qlist = qlist[0:len(qlist) - 1]
var topicList []TopicsRow 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.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 { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return return
@ -146,25 +159,18 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r) InternalError(err,w,r)
return return
} }
defer rows.Close()
topicItem := TopicsRow{ID: 0} var reqUserList map[int]bool = make(map[int]bool)
for rows.Next() { 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 { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return return
} }
topicItem.Link = build_topic_url(name_to_slug(topicItem.Title),topicItem.ID) 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) forum := fstore.DirtyGet(topicItem.ParentID)
if topicItem.ParentID >= 0 { if topicItem.ParentID >= 0 {
@ -183,17 +189,40 @@ func route_topics(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r) InternalError(err,w,r)
} }
if hooks["topics_trow_assign"] != nil { if hooks["topics_topic_row_assign"] != nil {
run_vhook("topics_trow_assign", &topicItem, &forum) 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() err = rows.Err()
if err != nil { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return 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} pi := TopicsPage{"Topic List",user,headerVars,topicList,extData}
if pre_render_hooks["pre_render_topic_list"] != nil { 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) InternalError(err,w,r)
return return
} }
defer rows.Close()
var topicList []TopicUser // 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 topicItem TopicUser = TopicUser{ID: 0} var topicList []*TopicsRow
var reqUserList map[int]bool = make(map[int]bool)
for rows.Next() { 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 { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return return
} }
topicItem.Link = build_topic_url(name_to_slug(topicItem.Title),topicItem.ID) 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) topicItem.LastReplyAt, err = relative_time(topicItem.LastReplyAt)
if err != nil { if err != nil {
InternalError(err,w,r) 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 { if hooks["forum_trow_assign"] != nil {
run_vhook("forum_trow_assign", &topicItem, &forum) 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() err = rows.Err()
if err != nil { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return 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} pi := ForumPage{forum.Name,user,headerVars,topicList,*forum,page,last_page,extData}
if pre_render_hooks["pre_render_view_forum"] != nil { 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) InternalError(err,w,r)
return return
} }
defer rows.Close()
replyItem := Reply{ClassName:""} replyItem := Reply{ClassName:""}
for rows.Next() { for rows.Next() {
@ -561,7 +607,6 @@ func route_topic_id(w http.ResponseWriter, r *http.Request, user User){
InternalError(err,w,r) InternalError(err,w,r)
return return
} }
rows.Close()
tpage := TopicPage{topic.Title,user,headerVars,replyList,topic,page,last_page,extData} tpage := TopicPage{topic.Title,user,headerVars,replyList,topic,page,last_page,extData}
if pre_render_hooks["pre_render_view_topic"] != nil { 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) 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 { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return return
@ -882,7 +927,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
_, err = add_replies_to_topic_stmt.Exec(1,tid) _, err = add_replies_to_topic_stmt.Exec(1,user.ID,tid)
if err != nil { if err != nil {
InternalError(err,w,r) InternalError(err,w,r)
return return
@ -1922,6 +1967,7 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
InternalErrorJS(err,w,r) InternalErrorJS(err,w,r)
return return
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
err = rows.Scan(&asid,&actor_id,&targetUser_id,&event,&elementType,&elementID) 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) InternalErrorJS(err,w,r)
return return
} }
rows.Close()
if len(msglist) != 0 { if len(msglist) != 0 {
msglist = msglist[0:len(msglist)-1] msglist = msglist[0:len(msglist)-1]

View File

@ -111,9 +111,9 @@ w.Write(forum_19)
} }
} }
w.Write(forum_20) w.Write(forum_20)
if item.Avatar != "" { if item.Creator.Avatar != "" {
w.Write(forum_21) w.Write(forum_21)
w.Write([]byte(item.Avatar)) w.Write([]byte(item.Creator.Avatar))
w.Write(forum_22) w.Write(forum_22)
} }
w.Write(forum_23) w.Write(forum_23)
@ -125,25 +125,40 @@ w.Write([]byte(item.Link))
w.Write(forum_26) w.Write(forum_26)
w.Write([]byte(item.Title)) w.Write([]byte(item.Title))
w.Write(forum_27) w.Write(forum_27)
w.Write([]byte(item.UserLink)) w.Write([]byte(item.Creator.Link))
w.Write(forum_28) w.Write(forum_28)
w.Write([]byte(item.CreatedByName)) w.Write([]byte(item.Creator.Name))
w.Write(forum_29) w.Write(forum_29)
if item.Is_Closed { if item.Is_Closed {
w.Write(forum_30) w.Write(forum_30)
} }
if item.Sticky {
w.Write(forum_31) w.Write(forum_31)
} }
} else {
w.Write(forum_32) w.Write(forum_32)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic { if item.LastUser.Avatar != "" {
w.Write(forum_33) 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_34)
} }
w.Write(forum_35) w.Write(forum_35)
} w.Write([]byte(item.LastUser.Link))
w.Write(forum_36) 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) w.Write(footer_0)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" { if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1) w.Write(footer_1)

View File

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

View File

@ -76,9 +76,9 @@ w.Write(topics_3)
} }
} }
w.Write(topics_4) w.Write(topics_4)
if item.Avatar != "" { if item.Creator.Avatar != "" {
w.Write(topics_5) w.Write(topics_5)
w.Write([]byte(item.Avatar)) w.Write([]byte(item.Creator.Avatar))
w.Write(topics_6) w.Write(topics_6)
} }
w.Write(topics_7) w.Write(topics_7)
@ -98,23 +98,38 @@ w.Write([]byte(item.ForumName))
w.Write(topics_14) w.Write(topics_14)
} }
w.Write(topics_15) w.Write(topics_15)
w.Write([]byte(item.UserLink)) w.Write([]byte(item.Creator.Link))
w.Write(topics_16) w.Write(topics_16)
w.Write([]byte(item.CreatedByName)) w.Write([]byte(item.Creator.Name))
w.Write(topics_17) w.Write(topics_17)
if item.Is_Closed { if item.Is_Closed {
w.Write(topics_18) w.Write(topics_18)
} }
if item.Sticky {
w.Write(topics_19) w.Write(topics_19)
} }
} else {
w.Write(topics_20) w.Write(topics_20)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { if item.LastUser.Avatar != "" {
w.Write(topics_21) w.Write(topics_21)
} w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_22) w.Write(topics_22)
} }
w.Write(topics_23) 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) w.Write(footer_0)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" { if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1) w.Write(footer_1)

View File

@ -13,6 +13,7 @@ import (
"text/template/parse" "text/template/parse"
) )
// TO-DO: Turn this file into a library
var ctemplates []string var ctemplates []string
var tmpl_ptr_map map[string]interface{} = make(map[string]interface{}) var tmpl_ptr_map map[string]interface{} = make(map[string]interface{})
var text_overlap_list map[string]int 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) { 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.dir = dir
c.doImports = true c.doImports = true
c.funcMap = map[string]interface{}{ 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) cur = cur.FieldByName(id)
if cur.Kind() == reflect.Interface { if cur.Kind() == reflect.Interface {
cur = cur.Elem() cur = cur.Elem()
// TO-DO: Surely, there's a better way of detecting this?
/*if cur.Kind() == reflect.String && cur.Type().Name() != "string" { /*if cur.Kind() == reflect.String && cur.Type().Name() != "string" {
varbit = "string(" + varbit + "." + id + ")"*/ varbit = "string(" + varbit + "." + id + ")"*/
//if cur.Kind() == reflect.String && cur.Type().Name() != "string" { //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 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) cur = cur.FieldByName(bit)
if cur.Kind() == reflect.Interface { if cur.Kind() == reflect.Interface {
cur = cur.Elem() cur = cur.Elem()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -251,6 +251,7 @@ a {
textarea { textarea {
background-color: #444444; background-color: #444444;
border-color: #555555; border-color: #555555;
color: #999999;
width: calc(100% - 15px); width: calc(100% - 15px);
min-height: 80px; min-height: 80px;
} }
@ -412,6 +413,23 @@ input, select, textarea {
content: "Locked"; 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 */ /* Profiles */
#profile_left_lane { #profile_left_lane {
width: 220px; width: 220px;
@ -453,6 +471,32 @@ input, select, textarea {
padding-left: 136px; 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) { @media(max-width: 935px) {
.simple .user_tag { .simple .user_tag {
display: none; 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) { @media(max-width: 470px) {
.menu_create_topic, .like_count_label, .like_count { .menu_create_topic, .like_count_label, .like_count {
display: none; display: none;

View File

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

36
user.go
View File

@ -233,6 +233,16 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
} }
} }
pusher, ok := w.(http.Pusher)
if ok {
pusher.Push("/static/main.css", nil)
pusher.Push("/static/global.js", nil)
pusher.Push("/static/jquery-3.1.1.min.js", nil)
// TO-DO: Push the theme CSS files
// TO-DO: Push the theme scripts
// TO-DO: Push avatars?
}
return headerVars, true return headerVars, true
} }
func _simple_panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (success bool) { 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 return headerVars, true
} }
@ -278,14 +298,6 @@ func _pre_route(w http.ResponseWriter, r *http.Request) (User,bool) {
return *user, true 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) host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
PreError("Bad IP",w,r) 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) { 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_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin
user.Is_Super_Mod = user.Is_Admin || groups[user.Group].Is_Mod user.Is_Super_Mod = user.Is_Admin || groups[user.Group].Is_Mod
user.Is_Mod = user.Is_Super_Mod user.Is_Mod = user.Is_Super_Mod

View File

@ -1,14 +1,17 @@
package main package main
import "log" import (
import "sync" "fmt"
import "errors" "log"
import "strings" "sync"
import "strconv" "errors"
import "database/sql" "strings"
"strconv"
"database/sql"
import "./query_gen/lib" "./query_gen/lib"
import "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
)
// TO-DO: Add the watchdog goroutine // TO-DO: Add the watchdog goroutine
var users UserStore var users UserStore
@ -19,6 +22,8 @@ type UserStore interface {
Get(id int) (*User, error) Get(id int) (*User, error)
GetUnsafe(id int) (*User, error) GetUnsafe(id int) (*User, error)
CascadeGet(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) BypassGet(id int) (*User, error)
Set(item *User) error Set(item *User) error
Add(item *User) error Add(item *User) error
@ -112,6 +117,114 @@ func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
return user, err 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) { func (sus *MemoryUserStore) BypassGet(id int) (*User, error) {
user := &User{ID:id,Loggedin:true} 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) 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 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) { func (sus *SqlUserStore) BypassGet(id int) (*User, error) {
user := User{ID:id,Loggedin:true} 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) 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)