Added the topic and user memory caches. Currently used in an extremely limited way, we plan to expand this!

Added a template minifier which strips out many types of whitespace.
Stop the router from blocking the other goroutines.
Content is no longer an interface{} in the TopicUser and Reply structs
Allow plugins to add new routes without any potential race coditions occuring
Use non-breaking spaces in the topic view to make it harder for the minifier to break things.
This commit is contained in:
Azareal 2017-02-11 14:51:16 +00:00
parent 534ef10194
commit 399128c208
20 changed files with 561 additions and 84 deletions

23
cache.go Normal file
View File

@ -0,0 +1,23 @@
package main
import "errors"
const CACHE_STATIC int = 0
const CACHE_DYNAMIC int = 1
const CACHE_SQL int = 2
var ErrStoreCapacityOverflow = errors.New("This datastore has already reached it's max capacity")
var users UserStore
var topics TopicStore
type DataStore interface {
Get(id int) (interface{}, error)
GetUnsafe(id int) (interface{}, error)
CascadeGet(id int) (interface{}, error)
Add(item interface{}) error
AddUnsafe(item interface{}) error
Remove(id int) error
RemoveUnsafe(id int) error
GetLength() int
GetCapacity() int
}

View File

@ -11,6 +11,9 @@ var dbport = "3306" // You probably won't need to change this
var max_request_size = 5 * megabyte
// Misc
var cache_topicuser = CACHE_STATIC
var user_cache_capacity = 100 // The max number of users held in memory
var topic_cache_capacity = 100 // The max number of topics held in memory
var default_route = route_topics
var default_group = 3 // Should be a setting
var activation_group = 5 // Should be a setting

View File

@ -112,6 +112,7 @@ CREATE TABLE `users_replies`(
CREATE TABLE `likes`(
`weight` tinyint DEFAULT 1 not null,
/*`type` tinyint not null, /* Regular Post = 1, Big Post = 2, Mega Post = 3, etc.*/
`targetItem` int not null,
`targetType` varchar(50) DEFAULT 'replies' not null,
`sentBy` int not null,

View File

@ -149,6 +149,9 @@ var dbport = "` + db_port + `" // You probably won't need to change this
var max_request_size = 5 * megabyte
// Misc
var cache_topicuser = CACHE_STATIC
var user_cache_capacity = 100 // The max number of users held in memory
var topic_cache_capacity = 100 // The max number of topics held in memory
var default_route = route_topics
var default_group = 3 // Should be a setting
var activation_group = 5 // Should be a setting

14
main.go
View File

@ -51,9 +51,9 @@ func compile_templates() {
log.Print("Compiling the templates")
topic := TopicUser{1,"Blah",template.HTML("Hey there!"),0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"","",no_css_tmpl,0,"","","","",58,false}
topic := TopicUser{1,"Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"",default_group,"",no_css_tmpl,0,"","","","",58,false}
var replyList []Reply
replyList = append(replyList, Reply{0,0,"",template.HTML("Yo!"),0,"","",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1})
replyList = append(replyList, Reply{0,0,"","Yo!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1})
var varList map[string]VarItem = make(map[string]VarItem)
tpage := TopicPage{"Title",user,noticeList,replyList,topic,1,1,false}
@ -80,7 +80,7 @@ func compile_templates() {
topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage", topics_page, varList)
var topicList []TopicUser
topicList = append(topicList,TopicUser{1,"Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"Admin","","",0,"","","","",58,false})
topicList = append(topicList,TopicUser{1,"Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"Admin",default_group,"","",0,"","","","",58,false})
forum_item := Forum{1,"General Forum",true,"all",0,"",0,"",0,""}
forum_page := ForumPage{"General Forum",user,noticeList,topicList,forum_item,1,1,nil}
forum_tmpl := c.compile_template("forum.html","templates/","ForumPage", forum_page, varList)
@ -143,6 +143,14 @@ func main(){
log.Fatal(err)
}
if cache_topicuser == CACHE_STATIC {
users = NewStaticUserStore(user_cache_capacity)
topics = NewStaticTopicStore(topic_cache_capacity)
} else {
users = NewSqlUserStore()
topics = NewSqlTopicStore()
}
log.Print("Loading the static files.")
err = filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
if f.IsDir() {

View File

@ -9,9 +9,11 @@ import "strconv"
import "encoding/json"
var db *sql.DB
var get_session_stmt *sql.Stmt
var get_user_stmt *sql.Stmt
var get_full_user_stmt *sql.Stmt
var get_topic_list_stmt *sql.Stmt
var get_topic_user_stmt *sql.Stmt
var get_topic_stmt *sql.Stmt
var get_topic_replies_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt
var get_forum_topics_stmt *sql.Stmt
@ -90,8 +92,20 @@ func init_database(err error) {
log.Fatal(err)
}
log.Print("Preparing get_session statement.")
/*log.Print("Preparing get_session statement.")
get_session_stmt, err = db.Prepare("select `uid`,`name`,`group`,`is_super_admin`,`session`,`email`,`avatar`,`message`,`url_prefix`,`url_name`,`level`,`score`,`last_ip` from `users` where `uid` = ? and `session` = ? AND `session` <> ''")
if err != nil {
log.Fatal(err)
}*/
log.Print("Preparing get_user statement.")
get_user_stmt, err = db.Prepare("select `name`,`group`,`is_super_admin`,`avatar`,`message`,`url_prefix`,`url_name`,`level` from `users` where `uid` = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing get_full_user statement.")
get_full_user_stmt, err = db.Prepare("select `name`,`group`,`is_super_admin`,`session`,`email`,`avatar`,`message`,`url_prefix`,`url_name`,`level`,`score`,`last_ip` from `users` where `uid` = ?")
if err != nil {
log.Fatal(err)
}
@ -108,6 +122,12 @@ func init_database(err error) {
log.Fatal(err)
}
log.Print("Preparing get_topic statement.")
get_topic_stmt, err = db.Prepare("select title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount from topics where tid = ?")
if err != nil {
log.Fatal(err)
}
log.Print("Preparing get_topic_replies statement.")
get_topic_replies_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 from replies left join users ON replies.createdBy = users.uid where tid = ?")
if err != nil {

View File

@ -1,5 +1,4 @@
package main
import "html/template"
func init() {
plugins["helloworld"] = NewPlugin("helloworld","Hello World","Azareal","http://github.com/Azareal","","","",init_helloworld,nil,deactivate_helloworld)
@ -17,7 +16,7 @@ func deactivate_helloworld() {
func helloworld_reply(data interface{}) interface{} {
reply := data.(Reply)
reply.Content = "Hello World!"
reply.ContentHtml = template.HTML("Hello World!")
reply.ContentHtml = "Hello World!"
reply.Tag = "Auto"
return reply
}

View File

@ -7,9 +7,10 @@ type Reply struct
ID int
ParentID int
Content string
ContentHtml template.HTML
ContentHtml string
CreatedBy int
CreatedByName string
Group int
CreatedAt string
LastEdit int
LastEditBy int

View File

@ -16,38 +16,45 @@ func NewRouter() *Router {
}
func (router *Router) Handle(pattern string, handle http.Handler) {
router.mu.Lock()
router.routes[pattern] = handle.ServeHTTP
router.mu.Unlock()
}
func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
router.mu.Lock()
router.routes[pattern] = handle
router.mu.Unlock()
}
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.mu.RLock()
defer router.mu.RUnlock()
if req.URL.Path[0] != '/' {
router.mu.RUnlock()
w.WriteHeader(405)
w.Write([]byte(""))
return
}
// Do something on the path to turn slashes facing the wrong way "\" into "/" slashes. If it's bytes, then alter the bytes in place for the maximum speed
// Do something on the path to turn slashes facing the wrong way "\" into "/" slashes. Like what? Wouldn't that be slow? Might need to move to fasthttp for this
handle, ok := router.routes[req.URL.Path]
if ok {
router.mu.RUnlock()
handle(w,req)
return
}
if req.URL.Path[len(req.URL.Path) - 1] == '/' {
router.mu.RUnlock()
NotFound(w,req)
return
}
handle, ok = router.routes[req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]]
if ok {
router.mu.RUnlock()
handle(w,req)
return
}
@ -55,10 +62,12 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
handle, ok = router.routes[req.URL.Path + "/"]
if ok {
router.mu.RUnlock()
handle(w,req)
return
}
router.mu.RUnlock()
NotFound(w,req)
return
}

View File

@ -288,23 +288,20 @@ func route_forums(w http.ResponseWriter, r *http.Request){
func route_topic_id(w http.ResponseWriter, r *http.Request){
var(
err error
content string
group int
page int
offset int
replyList []Reply
)
page, _ = strconv.Atoi(r.FormValue("page"))
topic := TopicUser{Css: no_css_tmpl}
topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):])
tid, err := strconv.Atoi(r.URL.Path[len("/topic/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.",w,r)
return
}
// Get the topic..
err = get_topic_user_stmt.QueryRow(topic.ID).Scan(&topic.Title, &content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.CreatedByName, &topic.Avatar, &group, &topic.URLPrefix, &topic.URLName, &topic.Level)
topic, err := get_topicuser(tid)
if err == sql.ErrNoRows {
NotFound(w,r)
return
@ -312,6 +309,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
InternalError(err,w,r)
return
}
topic.Css = no_css_tmpl
user, noticeList, ok := ForumSessionCheck(w,r,topic.ParentID)
if !ok {
@ -322,8 +320,8 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
return
}
topic.Content = template.HTML(parse_message(content))
topic.ContentLines = strings.Count(content,"\n")
topic.ContentLines = strings.Count(topic.Content,"\n")
topic.Content = parse_message(topic.Content)
// We don't want users posting in locked topics...
if topic.Is_Closed && !user.Is_Mod {
@ -337,12 +335,12 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
} else {
topic.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(topic.CreatedBy),1)
}
if groups[group].Is_Mod || groups[group].Is_Admin {
if groups[topic.Group].Is_Mod || groups[topic.Group].Is_Admin {
topic.Css = staff_css_tmpl
topic.Level = -1
}
topic.Tag = groups[group].Tag
topic.Tag = groups[topic.Group].Tag
if settings["url_tags"] == false {
topic.URLName = ""
@ -382,16 +380,16 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
replyItem := Reply{Css: no_css_tmpl}
for rows.Next() {
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IpAddress, &replyItem.LikeCount)
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &replyItem.Group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IpAddress, &replyItem.LikeCount)
if err != nil {
InternalError(err,w,r)
return
}
replyItem.ParentID = topic.ID
replyItem.ContentHtml = template.HTML(parse_message(replyItem.Content))
replyItem.ContentHtml = parse_message(replyItem.Content)
replyItem.ContentLines = strings.Count(replyItem.Content,"\n")
if groups[group].Is_Mod || groups[group].Is_Admin {
if groups[replyItem.Group].Is_Mod || groups[replyItem.Group].Is_Admin {
replyItem.Css = staff_css_tmpl
replyItem.Level = -1
} else {
@ -405,7 +403,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
replyItem.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(replyItem.CreatedBy),1)
}
replyItem.Tag = groups[group].Tag
replyItem.Tag = groups[replyItem.Group].Tag
/*if settings["url_tags"] == false {
replyItem.URLName = ""
@ -462,7 +460,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyCss template.CSS
replyLines int
replyTag string
group int
replyGroup int
replyList []Reply
)
@ -479,7 +477,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
puser = user
} else {
// Fetch the user data
err = db.QueryRow("select `name`,`group`,`is_super_admin`,`avatar`,`message`,`url_prefix`,`url_name`,`level` from `users` where `uid` = ?", puser.ID).Scan(&puser.Name, &puser.Group, &puser.Is_Super_Admin, &puser.Avatar, &puser.Message, &puser.URLPrefix, &puser.URLName, &puser.Level)
err = get_user_stmt.QueryRow(puser.ID).Scan(&puser.Name, &puser.Group, &puser.Is_Super_Admin, &puser.Avatar, &puser.Message, &puser.URLPrefix, &puser.URLName, &puser.Level)
if err == sql.ErrNoRows {
NotFound(w,r)
return
@ -516,14 +514,14 @@ func route_profile(w http.ResponseWriter, r *http.Request){
defer rows.Close()
for rows.Next() {
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &group)
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &replyGroup)
if err != nil {
InternalError(err,w,r)
return
}
replyLines = strings.Count(replyContent,"\n")
if groups[group].Is_Mod || groups[group].Is_Admin {
if groups[replyGroup].Is_Mod || groups[replyGroup].Is_Admin {
replyCss = staff_css_tmpl
} else {
replyCss = no_css_tmpl
@ -536,8 +534,8 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyAvatar = strings.Replace(noavatar,"{id}",strconv.Itoa(replyCreatedBy),1)
}
if groups[group].Tag != "" {
replyTag = groups[group].Tag
if groups[replyGroup].Tag != "" {
replyTag = groups[replyGroup].Tag
} else if puser.ID == replyCreatedBy {
replyTag = "Profile Owner"
} else {
@ -547,7 +545,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyLiked := false
replyLikeCount := 0
replyList = append(replyList, Reply{rid,puser.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","","",0,"",replyLiked,replyLikeCount})
replyList = append(replyList, Reply{rid,puser.ID,replyContent,parse_message(replyContent),replyCreatedBy,replyCreatedByName,replyGroup,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","","",0,"",replyLiked,replyLikeCount})
}
err = rows.Err()
if err != nil {

View File

@ -1,7 +1,7 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "strconv"
import "io"
import "strconv"
func init() {
template_forum_handle = template_forum

View File

@ -97,25 +97,25 @@ var topic_23 []byte = []byte(`</p>
var topic_24 []byte = []byte(`</textarea><br /><br />
<a href="/user/`)
var topic_25 []byte = []byte(`" class="username real_username">`)
var topic_26 []byte = []byte(`</a>
var topic_26 []byte = []byte(`</a>&nbsp;
<a href="/topic/like/submit/`)
var topic_27 []byte = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username" style="`)
var topic_28 []byte = []byte(`background-color:/*#eaffea*/#D6FFD6;`)
var topic_29 []byte = []byte(`">😀</button></a>
var topic_29 []byte = []byte(`">😀</button></a>&nbsp;
`)
var topic_30 []byte = []byte(`<a href='/topic/edit/`)
var topic_31 []byte = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username">🖊</button></a>
var topic_31 []byte = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username">🖊</button></a>&nbsp;
<a href='/topic/delete/submit/`)
var topic_32 []byte = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username">🗑</button></a>
var topic_32 []byte = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username">🗑</button></a>&nbsp;
`)
var topic_33 []byte = []byte(`<a class="mod_button" href='/topic/unstick/submit/`)
var topic_34 []byte = []byte(`' style="font-weight:normal;" title="Unpin Topic"><button class="username" style="background-color:/*#eaffea*/#D6FFD6;">📌</button></a>`)
var topic_35 []byte = []byte(`<a href='/topic/stick/submit/`)
var topic_36 []byte = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username">📌</button></a>`)
var topic_37 []byte = []byte(`
var topic_37 []byte = []byte(`&nbsp;
<a href="/report/submit/`)
var topic_38 []byte = []byte(`?session=`)
var topic_39 []byte = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username">🚩</button></a>
var topic_39 []byte = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username">🚩</button></a>&nbsp;
`)
var topic_40 []byte = []byte(`<a class="username hide_on_micro" style="float: right;color:#505050;border-left:none;padding-left:5px;padding-right:5px;font-size:17px;">`)
var topic_41 []byte = []byte(`</a><a class="username hide_on_micro" style="color:#505050;float:right;opacity:0.85;margin-left:5px;" title="Like Count">😀</a>`)
@ -138,20 +138,20 @@ var topic_52 []byte = []byte(`">
var topic_53 []byte = []byte(`</p><br /><br />
<a href="/user/`)
var topic_54 []byte = []byte(`" class="username real_username">`)
var topic_55 []byte = []byte(`</a>
var topic_55 []byte = []byte(`</a>&nbsp;
<a href="/reply/like/submit/`)
var topic_56 []byte = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username" style="`)
var topic_57 []byte = []byte(`background-color:/*#eaffea*/#D6FFD6;`)
var topic_58 []byte = []byte(`">😀</button></a>
var topic_58 []byte = []byte(`">😀</button></a>&nbsp;
`)
var topic_59 []byte = []byte(`<a href="/reply/edit/submit/`)
var topic_60 []byte = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item">🖊️</button></a> `)
var topic_60 []byte = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item">🖊️</button></a>&nbsp;`)
var topic_61 []byte = []byte(`<a href="/reply/delete/submit/`)
var topic_62 []byte = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item">🗑️</button></a> `)
var topic_62 []byte = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item">🗑️</button></a>&nbsp;`)
var topic_63 []byte = []byte(`
<a href="/report/submit/`)
var topic_64 []byte = []byte(`?session=`)
var topic_65 []byte = []byte(`&type=reply" class="mod_button" title="Flag Reply"><button class="username report_item">🚩</button></a>
var topic_65 []byte = []byte(`&type=reply" class="mod_button" title="Flag Reply"><button class="username report_item">🚩</button></a>&nbsp;
`)
var topic_66 []byte = []byte(`<a class="username hide_on_micro" style="float: right;color:#505050;border-left:none;padding-left:5px;padding-right:5px;font-size:17px;">`)
var topic_67 []byte = []byte(`</a><a class="username hide_on_micro" style="color:#505050;float:right;opacity:0.85;margin-left:5px;" title="Like Count">😀</a>`)

View File

@ -85,7 +85,7 @@ w.Write(profile_19)
w.Write([]byte(string(item.Css)))
}
w.Write(profile_20)
w.Write([]byte(string(item.ContentHtml)))
w.Write([]byte(item.ContentHtml))
w.Write(profile_21)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(profile_22)

View File

@ -1,7 +1,6 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "strconv"
import "html/template"
import "io"
func init() {
@ -92,9 +91,9 @@ w.Write(topic_21)
w.Write([]byte(string(tmpl_topic_vars.Topic.Css)))
}
w.Write(topic_22)
w.Write([]byte(string(tmpl_topic_vars.Topic.Content.(template.HTML))))
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_23)
w.Write([]byte(string(tmpl_topic_vars.Topic.Content.(template.HTML))))
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_24)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.CreatedBy)))
w.Write(topic_25)
@ -156,7 +155,7 @@ w.Write(topic_51)
w.Write([]byte(string(item.Css)))
}
w.Write(topic_52)
w.Write([]byte(string(item.ContentHtml)))
w.Write([]byte(item.ContentHtml))
w.Write(topic_53)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(topic_54)

View File

@ -2,7 +2,6 @@
package main
import "io"
import "strconv"
import "html/template"
func init() {
template_topic_alt_handle = template_topic_alt
@ -117,9 +116,9 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.Level)))
w.Write(topic_alt_34)
}
w.Write(topic_alt_35)
w.Write([]byte(string(tmpl_topic_alt_vars.Topic.Content.(template.HTML))))
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_36)
w.Write([]byte(string(tmpl_topic_alt_vars.Topic.Content.(template.HTML))))
w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_37)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_38)
@ -146,7 +145,7 @@ w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_48)
}
w.Write(topic_alt_49)
w.Write([]byte(string(item.ContentHtml)))
w.Write([]byte(item.ContentHtml))
w.Write(topic_alt_50)
if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply {
w.Write(topic_alt_51)

View File

@ -1,7 +1,7 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "io"
import "strconv"
import "io"
func init() {
template_topics_handle = template_topics

View File

@ -12,6 +12,11 @@ import "text/template/parse"
var ctemplates []string
var tmpl_ptr_map map[string]interface{} = make(map[string]interface{})
var text_overlap_list map[string]int
func init() {
text_overlap_list = make(map[string]int)
}
type VarItem struct
{
@ -80,7 +85,11 @@ func (c *CTemplateSet) compile_template(name string, dir string, expects string,
if err != nil {
log.Fatal(err)
}
content := string(res)
if !debug {
content = minify(content)
}
tree := parse.New(name, c.funcMap)
var treeSet map[string]*parse.Tree = make(map[string]*parse.Tree)
@ -822,7 +831,11 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle
if err != nil {
log.Fatal(err)
}
content := string(res)
if !debug {
content = minify(content)
}
tree := parse.New(node.Name, c.funcMap)
var treeSet map[string]*parse.Tree = make(map[string]*parse.Tree)
@ -859,4 +872,13 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle
func (c *CTemplateSet) compile_command(*parse.CommandNode) (out string) {
return ""
}
}
func minify(data string) string {
data = strings.Replace(data,"\t","",-1)
data = strings.Replace(data,"\v","",-1)
data = strings.Replace(data,"\n","",-1)
data = strings.Replace(data,"\r","",-1)
data = strings.Replace(data," "," ",-1)
return data
}

View File

@ -22,13 +22,13 @@
<div class="rowitem passive editable_parent post_item" style="border-bottom: none;{{if .Topic.Avatar}}background-image:url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;background-size:128px;padding-left:136px;{{.Topic.Css}}{{end}}">
<p class="hide_on_edit topic_content user_content" style="margin:0;padding:0;">{{.Topic.Content}}</p>
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea><br /><br />
<a href="/user/{{.Topic.CreatedBy}}" class="username real_username">{{.Topic.CreatedByName}}</a>
<a href="/topic/like/submit/{{.Topic.ID}}" class="mod_button" title="Love it" style="color:#202020;"><button class="username" style="{{if .Topic.Liked}}background-color:/*#eaffea*/#D6FFD6;{{end}}">😀</button></a>
{{if .CurrentUser.Is_Mod}}<a href='/topic/edit/{{.Topic.ID}}' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username">🖊️</button></a>
<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username">🗑️</button></a>
<a href="/user/{{.Topic.CreatedBy}}" class="username real_username">{{.Topic.CreatedByName}}</a>&nbsp;
<a href="/topic/like/submit/{{.Topic.ID}}" class="mod_button" title="Love it" style="color:#202020;"><button class="username" style="{{if .Topic.Liked}}background-color:/*#eaffea*/#D6FFD6;{{end}}">😀</button></a>&nbsp;
{{if .CurrentUser.Is_Mod}}<a href='/topic/edit/{{.Topic.ID}}' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username">🖊️</button></a>&nbsp;
<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username">🗑️</button></a>&nbsp;
{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unpin Topic"><button class="username" style="background-color:/*#eaffea*/#D6FFD6;">📌</button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username">📌</button></a>{{end}}
{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username">🚩</button></a>
{{end}}&nbsp;
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username">🚩</button></a>&nbsp;
{{if .Topic.LikeCount}}<a class="username hide_on_micro" style="float: right;color:#505050;border-left:none;padding-left:5px;padding-right:5px;font-size:17px;">{{.Topic.LikeCount}}</a><a class="username hide_on_micro" style="color:#505050;float:right;opacity:0.85;margin-left:5px;" title="Like Count">😀</a>{{end}}
{{if .Topic.Tag}}<a class="username hide_on_micro" style="float:right;color:#505050;font-size:16px;">{{.Topic.Tag}}</a>{{else}}<a class="username hide_on_micro" style="float: right;color:#505050;border-left:none;padding-left:5px;padding-right:5px;font-size:17px;">{{.Topic.Level}}</a><a class="username hide_on_micro" style="color:#505050;float:right;opacity:0.85;" title="Level">👑</a>{{end}}
</div>
@ -36,11 +36,11 @@
<div class="rowblock post_container" style="overflow: hidden;">{{range .ItemList}}
<div class="rowitem rowhead passive deletable_block editable_parent post_item" style="{{if .Avatar}}background-image:url({{.Avatar}}), url(/static/white-dot.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;background-size:128px;padding-left:136px;{{.Css}}{{end}}">
<p class="editable_block user_content" style="margin: 0;padding: 0;">{{.ContentHtml}}</p><br /><br />
<a href="/user/{{.CreatedBy}}" class="username real_username">{{.CreatedByName}}</a>
<a href="/reply/like/submit/{{.ID}}" class="mod_button" title="Love it" style="color:#202020;"><button class="username" style="{{if .Liked}}background-color:/*#eaffea*/#D6FFD6;{{end}}">😀</button></a>
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Reply"><button class="username edit_item">🖊️</button></a> {{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Reply"><button class="username delete_item">🗑️</button></a> {{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="mod_button" title="Flag Reply"><button class="username report_item">🚩</button></a>
<a href="/user/{{.CreatedBy}}" class="username real_username">{{.CreatedByName}}</a>&nbsp;
<a href="/reply/like/submit/{{.ID}}" class="mod_button" title="Love it" style="color:#202020;"><button class="username" style="{{if .Liked}}background-color:/*#eaffea*/#D6FFD6;{{end}}">😀</button></a>&nbsp;
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Reply"><button class="username edit_item">🖊️</button></a>&nbsp;{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Reply"><button class="username delete_item">🗑️</button></a>&nbsp;{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="mod_button" title="Flag Reply"><button class="username report_item">🚩</button></a>&nbsp;
{{if .LikeCount}}<a class="username hide_on_micro" style="float: right;color:#505050;border-left:none;padding-left:5px;padding-right:5px;font-size:17px;">{{.LikeCount}}</a><a class="username hide_on_micro" style="color:#505050;float:right;opacity:0.85;margin-left:5px;" title="Like Count">😀</a>{{end}}
{{if .Tag}}<a class="username hide_on_micro" style="float: right;color:#505050;font-size:16px;">{{.Tag}}</a>{{else}}<a class="username hide_on_micro" style="float: right;color:#505050;border-left:none;padding-left:5px;padding-right:5px;font-size:17px;">{{.Level}}</a><a class="username hide_on_micro" style="color:#505050;float:right;opacity:0.85;" title="Level">👑{{end}}</a>
</div>

238
topic.go
View File

@ -1,5 +1,8 @@
package main
//import "fmt"
import "sync"
import "html/template"
import "database/sql"
type Topic struct
{
@ -10,17 +13,19 @@ type Topic struct
Is_Closed bool
Sticky bool
CreatedAt string
LastReplyAt string
ParentID int
Status string // Deprecated. Marked for removal.
IpAddress string
PostCount int
LikeCount int
}
type TopicUser struct
{
ID int
Title string
Content interface{}
Content string
CreatedBy int
Is_Closed bool
Sticky bool
@ -33,6 +38,7 @@ type TopicUser struct
LikeCount int
CreatedByName string
Group int
Avatar string
Css template.CSS
ContentLines int
@ -48,7 +54,7 @@ type TopicsRow struct
{
ID int
Title string
Content interface{}
Content string
CreatedBy int
Is_Closed bool
Sticky bool
@ -72,3 +78,231 @@ type TopicsRow struct
ForumName string //TopicsRow
}
type TopicStore interface {
Get(id int) (*Topic, error)
GetUnsafe(id int) (*Topic, error)
CascadeGet(id int) (*Topic, error)
Add(item *Topic) error
AddUnsafe(item *Topic) error
Remove(id int) error
RemoveUnsafe(id int) error
AddLastTopic(item *Topic, fid int) error
GetLength() int
GetCapacity() int
}
type StaticTopicStore struct {
items map[int]*Topic
length int
capacity int
mu sync.RWMutex
}
func NewStaticTopicStore(capacity int) *StaticTopicStore {
return &StaticTopicStore{items:make(map[int]*Topic),capacity:capacity}
}
func (sts *StaticTopicStore) Get(id int) (*Topic, error) {
sts.mu.RLock()
item, ok := sts.items[id]
sts.mu.RUnlock()
if ok {
return item, nil
}
return item, sql.ErrNoRows
}
func (sts *StaticTopicStore) GetUnsafe(id int) (*Topic, error) {
item, ok := sts.items[id]
if ok {
return item, nil
}
return item, sql.ErrNoRows
}
func (sts *StaticTopicStore) CascadeGet(id int) (*Topic, error) {
sts.mu.RLock()
topic, ok := sts.items[id]
sts.mu.RUnlock()
if ok {
return topic, nil
}
topic = &Topic{ID:id}
err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
if err == nil {
sts.Add(topic)
}
return topic, err
}
func (sts *StaticTopicStore) Add(item *Topic) error {
if sts.length >= sts.capacity {
return ErrStoreCapacityOverflow
}
sts.mu.Lock()
sts.items[item.ID] = item
sts.mu.Unlock()
sts.length++
return nil
}
func (sts *StaticTopicStore) AddUnsafe(item *Topic) error {
if sts.length >= sts.capacity {
return ErrStoreCapacityOverflow
}
sts.items[item.ID] = item
sts.length++
return nil
}
func (sts *StaticTopicStore) Remove(id int) error {
sts.mu.Lock()
delete(sts.items,id)
sts.mu.Unlock()
sts.length--
return nil
}
func (sts *StaticTopicStore) RemoveUnsafe(id int) error {
delete(sts.items,id)
sts.length--
return nil
}
func (sts *StaticTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon...
return nil
}
func (sts *StaticTopicStore) GetLength() int {
return sts.length
}
func (sts *StaticTopicStore) SetCapacity(capacity int) {
sts.capacity = capacity
}
func (sts *StaticTopicStore) GetCapacity() int {
return sts.capacity
}
//type DynamicTopicStore struct {
// items_expiries list.List
// items map[int]*Topic
//}
type SqlTopicStore struct {
}
func NewSqlTopicStore() *SqlTopicStore {
return &SqlTopicStore{}
}
func (sus *SqlTopicStore) Get(id int) (*Topic, error) {
topic := Topic{ID:id}
err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
return &topic, err
}
func (sus *SqlTopicStore) GetUnsafe(id int) (*Topic, error) {
topic := Topic{ID:id}
err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
return &topic, err
}
func (sus *SqlTopicStore) CascadeGet(id int) (*Topic, error) {
topic := Topic{ID:id}
err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
return &topic, err
}
// Placeholder methods, the actual queries are done elsewhere
func (sus *SqlTopicStore) Add(item *Topic) error {
return nil
}
func (sus *SqlTopicStore) AddUnsafe(item *Topic) error {
return nil
}
func (sus *SqlTopicStore) Remove(id int) error {
return nil
}
func (sus *SqlTopicStore) RemoveUnsafe(id int) error {
return nil
}
func (sts *SqlTopicStore) AddLastTopic(item *Topic, fid int) error {
// Coming Soon...
return nil
}
func (sts *SqlTopicStore) GetCapacity() int {
return 0
}
func (sus *SqlTopicStore) GetLength() int {
// Return the total number of topics on the forums
return 0
}
func get_topicuser(tid int) (TopicUser,error) {
if cache_topicuser != CACHE_SQL {
topic, err := topics.Get(tid)
if err == nil {
user, err := users.CascadeGet(topic.CreatedBy)
if err != nil {
return TopicUser{ID:tid}, err
}
// We might be better off just passing seperate topic and user structs to the caller?
return copy_topic_to_topicuser(topic, user), nil
} else if users.GetLength() < users.GetCapacity() {
topic, err = topics.CascadeGet(tid)
if err != nil {
return TopicUser{ID:tid}, err
}
user, err := users.CascadeGet(topic.CreatedBy)
if err != nil {
return TopicUser{ID:tid}, err
}
tu := copy_topic_to_topicuser(topic, user)
//fmt.Printf("%+v\n", topic)
//fmt.Println("")
//fmt.Printf("%+v\n", user)
//fmt.Println("")
//fmt.Printf("%+v\n", tu)
//fmt.Println("")
return tu, nil
}
}
tu := TopicUser{ID:tid}
err := get_topic_user_stmt.QueryRow(tid).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)
the_topic := Topic{ID:tu.ID, Title:tu.Title, Content:tu.Content, CreatedBy:tu.CreatedBy, Is_Closed:tu.Is_Closed, Sticky:tu.Sticky, CreatedAt:tu.CreatedAt, LastReplyAt:tu.LastReplyAt, ParentID:tu.ParentID, IpAddress:tu.IpAddress, PostCount:tu.PostCount, LikeCount:tu.LikeCount}
//fmt.Printf("%+v\n", the_topic)
topics.Add(&the_topic)
return tu, err
}
func copy_topic_to_topicuser(topic *Topic, user *User) (tu TopicUser) {
tu.CreatedByName = user.Name
tu.Group = user.Group
tu.Avatar = user.Avatar
tu.URLPrefix = user.URLPrefix
tu.URLName = user.URLName
tu.Level = user.Level
tu.ID = topic.ID
tu.Title = topic.Title
tu.Content = topic.Content
tu.CreatedBy = topic.CreatedBy
tu.Is_Closed = topic.Is_Closed
tu.Sticky = topic.Sticky
tu.CreatedAt = topic.CreatedAt
tu.LastReplyAt = topic.LastReplyAt
tu.ParentID = topic.ParentID
tu.IpAddress = topic.IpAddress
tu.PostCount = topic.PostCount
tu.LikeCount = topic.LikeCount
return tu
}

194
user.go
View File

@ -1,5 +1,6 @@
package main
//import "fmt"
import "sync"
import "strings"
import "strconv"
import "net"
@ -42,6 +43,161 @@ type Email struct
Token string
}
type UserStore interface {
Get(id int) (*User, error)
GetUnsafe(id int) (*User, error)
CascadeGet(id int) (*User, error)
Add(item *User) error
AddUnsafe(item *User) error
Remove(id int) error
RemoveUnsafe(id int) error
GetLength() int
GetCapacity() int
}
type StaticUserStore struct {
items map[int]*User
length int
capacity int
mu sync.RWMutex
}
func NewStaticUserStore(capacity int) *StaticUserStore {
return &StaticUserStore{items:make(map[int]*User),capacity:capacity}
}
func (sts *StaticUserStore) Get(id int) (*User, error) {
sts.mu.RLock()
item, ok := sts.items[id]
sts.mu.RUnlock()
if ok {
return item, nil
}
return item, sql.ErrNoRows
}
func (sts *StaticUserStore) GetUnsafe(id int) (*User, error) {
item, ok := sts.items[id]
if ok {
return item, nil
}
return item, sql.ErrNoRows
}
func (sts *StaticUserStore) CascadeGet(id int) (*User, error) {
sts.mu.RLock()
user, ok := sts.items[id]
sts.mu.RUnlock()
if ok {
return user, nil
}
user = &User{ID:id}
err := get_full_user_stmt.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)
if err == nil {
sts.Add(user)
}
return user, err
}
func (sts *StaticUserStore) Add(item *User) error {
if sts.length >= sts.capacity {
return ErrStoreCapacityOverflow
}
sts.mu.Lock()
sts.items[item.ID] = item
sts.mu.Unlock()
sts.length++
return nil
}
func (sts *StaticUserStore) AddUnsafe(item *User) error {
if sts.length >= sts.capacity {
return ErrStoreCapacityOverflow
}
sts.items[item.ID] = item
sts.length++
return nil
}
func (sts *StaticUserStore) Remove(id int) error {
sts.mu.Lock()
delete(sts.items,id)
sts.mu.Unlock()
sts.length--
return nil
}
func (sts *StaticUserStore) RemoveUnsafe(id int) error {
delete(sts.items,id)
sts.length--
return nil
}
func (sts *StaticUserStore) GetLength() int {
return sts.length
}
func (sts *StaticUserStore) SetCapacity(capacity int) {
sts.capacity = capacity
}
func (sts *StaticUserStore) GetCapacity() int {
return sts.capacity
}
//type DynamicUserStore struct {
// items_expiries list.List
// items map[int]*User
//}
type SqlUserStore struct {
}
func NewSqlUserStore() *SqlUserStore {
return &SqlUserStore{}
}
func (sus *SqlUserStore) Get(id int) (*User, error) {
user := User{ID:id}
err := get_full_user_stmt.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)
return &user, err
}
func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
user := User{ID:id}
err := get_full_user_stmt.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)
return &user, err
}
func (sus *SqlUserStore) CascadeGet(id int) (*User, error) {
user := User{ID:id}
err := get_full_user_stmt.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)
return &user, err
}
// Placeholder methods, the actual queries are done elsewhere
func (sus *SqlUserStore) Add(item *User) error {
return nil
}
func (sus *SqlUserStore) AddUnsafe(item *User) error {
return nil
}
func (sus *SqlUserStore) Remove(id int) error {
return nil
}
func (sus *SqlUserStore) RemoveUnsafe(id int) error {
return nil
}
func (sus *SqlUserStore) GetCapacity() int {
return 0
}
func (sus *SqlUserStore) GetLength() int {
// Return the total number of users registered on the forums
return 0
}
func SetPassword(uid int, password string) (error) {
salt, err := GenerateSafeString(saltLength)
if err != nil {
@ -128,38 +284,40 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, noticeList
return user, noticeList, success
}
func SimpleSessionCheck(w http.ResponseWriter, r *http.Request) (user User, success bool) {
func SimpleSessionCheck(w http.ResponseWriter, r *http.Request) (User,bool) {
// Are there any session cookies..?
cookie, err := r.Cookie("uid")
if err != nil {
user.Group = 6
user.Perms = GuestPerms
return user, true
return User{ID:0,Group:6,Perms:GuestPerms}, true
}
user.ID, err = strconv.Atoi(cookie.Value)
uid, err := strconv.Atoi(cookie.Value)
if err != nil {
user.Group = 6
user.Perms = GuestPerms
return user, true
return User{ID:0,Group:6,Perms:GuestPerms}, true
}
cookie, err = r.Cookie("session")
if err != nil {
user.Group = 6
user.Perms = GuestPerms
return user, true
return User{ID:0,Group:6,Perms:GuestPerms}, true
}
// Is this session valid..?
err = get_session_stmt.QueryRow(user.ID,cookie.Value).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)
user, err := users.CascadeGet(uid)
if err == sql.ErrNoRows {
user.ID = 0
user.Session = ""
user.Group = 6
user.Perms = GuestPerms
return user, true
return *user, true
} else if err != nil {
InternalError(err,w,r)
return user, false
return *user, false
}
if user.Session == "" || cookie.Value != user.Session {
user.ID = 0
user.Session = ""
user.Group = 6
user.Perms = GuestPerms
return *user, true
}
user.Is_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin
@ -187,17 +345,17 @@ func SimpleSessionCheck(w http.ResponseWriter, r *http.Request) (user User, succ
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return user, false
PreError("Bad IP",w,r)
return *user, false
}
if host != user.Last_IP {
_, err = update_last_ip_stmt.Exec(host, user.ID)
if err != nil {
InternalError(err,w,r)
return user, false
return *user, false
}
}
return user, true
return *user, true
}
func words_to_score(wcount int, topic bool) (score int) {