From 29d9d07cde0468e03e55fe99ee7318aaf79024c5 Mon Sep 17 00:00:00 2001 From: Azareal Date: Mon, 12 Jun 2017 10:03:14 +0100 Subject: [PATCH] Added Live Alerts for replies to posts. Added relative times to posts. Added push_alerts to the WebSockets Hub. Added simple_delete, purge, and simple_inner_join to the query generator. Query generator joiners now take not equals into account. Other query generator stuff and query refactoring. Fixed a weird padding bug on Tempra Conflux. Fixed a couple of megapost_min_chars and bigpost_min_chars which I missed. Fixed a security exploit in the login system. --- alerts.go | 37 +++ gen_mysql.go | 91 +++++- main.go | 4 +- mod_routes.go | 48 ++-- mysql.go | 45 +-- no_websockets.go | 4 + panel_routes.go | 1 - public/global.js | 3 +- query_gen/main.go | 384 ++++---------------------- query_gen/mysql.go | 134 ++++++++- query_gen/utils.go | 341 +++++++++++++++++++++++ routes.go | 103 ++++--- template_list.go | 92 +++--- template_topic_alt.go | 114 ++++---- templates/topic_alt.html | 35 ++- themes/tempra-conflux/public/main.css | 1 - topic.go | 22 +- user.go | 20 +- websockets.go | 45 ++- 19 files changed, 928 insertions(+), 596 deletions(-) create mode 100644 query_gen/utils.go diff --git a/alerts.go b/alerts.go index 4e72774f..9956f13c 100644 --- a/alerts.go +++ b/alerts.go @@ -1,8 +1,10 @@ package main +import "log" import "strings" import "strconv" import "errors" +import "database/sql" /* "You received a friend invite from {user}" @@ -111,3 +113,38 @@ func build_alert(event string, elementType string, actor_id int, targetUser_id i return `{"msg":"{0} ` + start_frag + act + post_act + ` {1}` + end_frag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `"}`, nil } + +func notify_watchers(asid int64) { + rows, err := get_watchers_stmt.Query(asid) + if err != nil && err != sql.ErrNoRows { + log.Fatal(err.Error()) + return + } + + var uid int + var uids []int + for rows.Next() { + err := rows.Scan(&uid) + if err != nil { + log.Fatal(err.Error()) + return + } + uids = append(uids,uid) + } + err = rows.Err() + if err != nil { + log.Fatal(err.Error()) + return + } + rows.Close() + + var actor_id, targetUser_id, elementID int + var event, elementType string + err = get_activity_entry_stmt.QueryRow(asid).Scan(&actor_id, &targetUser_id, &event, &elementType, &elementID) + if err != nil && err != sql.ErrNoRows { + log.Fatal(err.Error()) + return + } + + _ = ws_hub.push_alerts(uids, event, elementType, actor_id, targetUser_id, elementID) +} diff --git a/gen_mysql.go b/gen_mysql.go index 0675c54f..138266d1 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -26,14 +26,19 @@ var get_user_reply_uid_stmt *sql.Stmt var has_liked_topic_stmt *sql.Stmt var has_liked_reply_stmt *sql.Stmt var get_user_name_stmt *sql.Stmt +var get_user_rank_stmt *sql.Stmt +var get_user_active_stmt *sql.Stmt +var get_user_group_stmt *sql.Stmt var get_emails_by_user_stmt *sql.Stmt var get_topic_basic_stmt *sql.Stmt +var get_activity_entry_stmt *sql.Stmt var get_topic_list_stmt *sql.Stmt var get_topic_user_stmt *sql.Stmt var get_topic_by_reply_stmt *sql.Stmt var get_topic_replies_stmt *sql.Stmt var get_forum_topics_stmt *sql.Stmt var get_profile_replies_stmt *sql.Stmt +var get_watchers_stmt *sql.Stmt var create_topic_stmt *sql.Stmt var create_report_stmt *sql.Stmt var create_reply_stmt *sql.Stmt @@ -42,7 +47,9 @@ var create_like_stmt *sql.Stmt var add_activity_stmt *sql.Stmt var notify_one_stmt *sql.Stmt var register_stmt *sql.Stmt +var add_email_stmt *sql.Stmt var create_profile_reply_stmt *sql.Stmt +var add_subscription_stmt *sql.Stmt var create_forum_stmt *sql.Stmt var add_forum_perms_to_forum_stmt *sql.Stmt var add_plugin_stmt *sql.Stmt @@ -86,6 +93,10 @@ var update_user_stmt *sql.Stmt var update_group_perms_stmt *sql.Stmt var update_group_rank_stmt *sql.Stmt var update_group_stmt *sql.Stmt +var delete_reply_stmt *sql.Stmt +var delete_topic_stmt *sql.Stmt +var delete_profile_reply_stmt *sql.Stmt +var delete_forum_perms_by_forum_stmt *sql.Stmt func gen_mysql() (err error) { if debug { @@ -105,7 +116,7 @@ func gen_mysql() (err error) { } 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` = ?") + get_topic_stmt, err = db.Prepare("SELECT `title`,`content`,`createdBy`,`createdAt`,`is_closed`,`sticky`,`parentID`,`ipaddress`,`postCount`,`likeCount`,`data` FROM `topics` WHERE `tid` = ?") if err != nil { return err } @@ -212,6 +223,24 @@ func gen_mysql() (err error) { return err } + log.Print("Preparing get_user_rank statement.") + get_user_rank_stmt, err = db.Prepare("SELECT `group`,`is_super_admin` FROM `users` WHERE `uid` = ?") + if err != nil { + return err + } + + log.Print("Preparing get_user_active statement.") + get_user_active_stmt, err = db.Prepare("SELECT `active` FROM `users` WHERE `uid` = ?") + if err != nil { + return err + } + + log.Print("Preparing get_user_group statement.") + get_user_group_stmt, err = db.Prepare("SELECT `group` FROM `users` WHERE `uid` = ?") + if err != nil { + return err + } + log.Print("Preparing get_emails_by_user statement.") get_emails_by_user_stmt, err = db.Prepare("SELECT `email`,`validated` FROM `emails` WHERE `uid` = ?") if err != nil { @@ -224,38 +253,50 @@ func gen_mysql() (err error) { return err } + log.Print("Preparing get_activity_entry statement.") + get_activity_entry_stmt, err = db.Prepare("SELECT `actor`,`targetUser`,`event`,`elementType`,`elementID` FROM `activity_stream` WHERE `asid` = ?") + if err != nil { + return err + } + log.Print("Preparing get_topic_list statement.") - get_topic_list_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`parentID`,`users`.`name`,`users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy`=`users`.`uid` ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") + get_topic_list_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`parentID`,`users`.`name`,`users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") if err != nil { return err } log.Print("Preparing get_topic_user statement.") - get_topic_user_stmt, err = db.Prepare("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` = ?") + get_topic_user_stmt, err = db.Prepare("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` = ?") if err != nil { return err } log.Print("Preparing get_topic_by_reply statement.") - get_topic_by_reply_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`createdAt`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`parentID`,`topics`.`ipaddress`,`topics`.`postCount`,`topics`.`likeCount` FROM `replies` LEFT JOIN `topics` ON `replies`.`tid`=`topics`.`tid` WHERE `rid` = ?") + get_topic_by_reply_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`createdAt`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`parentID`,`topics`.`ipaddress`,`topics`.`postCount`,`topics`.`likeCount`,`topics`.`data` FROM `replies` LEFT JOIN `topics` ON `replies`.`tid` = `topics`.`tid` WHERE `rid` = ?") if err != nil { return 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` = ?") + 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 { return err } log.Print("Preparing get_forum_topics statement.") - get_forum_topics_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`lastReplyAt`,`topics`.`parentID`,`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") + get_forum_topics_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`lastReplyAt`,`topics`.`parentID`,`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") if err != nil { return err } log.Print("Preparing get_profile_replies statement.") - get_profile_replies_stmt, err = db.Prepare("SELECT `users_replies`.`rid`,`users_replies`.`content`,`users_replies`.`createdBy`,`users_replies`.`createdAt`,`users_replies`.`lastEdit`,`users_replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group` FROM `users_replies` LEFT JOIN `users` ON `users_replies`.`createdBy`=`users`.`uid` WHERE `users_replies`.`uid` = ?") + get_profile_replies_stmt, err = db.Prepare("SELECT `users_replies`.`rid`,`users_replies`.`content`,`users_replies`.`createdBy`,`users_replies`.`createdAt`,`users_replies`.`lastEdit`,`users_replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group` FROM `users_replies` LEFT JOIN `users` ON `users_replies`.`createdBy` = `users`.`uid` WHERE `users_replies`.`uid` = ?") + if err != nil { + return err + } + + log.Print("Preparing get_watchers statement.") + get_watchers_stmt, err = db.Prepare("SELECT `activity_subscriptions`.`user` FROM `activity_stream` INNER JOIN `activity_subscriptions` ON `activity_subscriptions`.`targetType` = `activity_stream`.`elementType` AND `activity_subscriptions`.`targetID` = `activity_stream`.`elementID` AND `activity_subscriptions`.`user` != `activity_stream`.`actor` WHERE `asid` = ?") if err != nil { return err } @@ -308,12 +349,24 @@ func gen_mysql() (err error) { return err } + log.Print("Preparing add_email statement.") + add_email_stmt, err = db.Prepare("INSERT INTO `emails`(`email`,`uid`,`validated`,`token`) VALUES (?,?,?,?)") + if err != nil { + return err + } + log.Print("Preparing create_profile_reply statement.") create_profile_reply_stmt, err = db.Prepare("INSERT INTO `users_replies`(`uid`,`content`,`parsed_content`,`createdAt`,`createdBy`) VALUES (?,?,?,NOW(),?)") if err != nil { return err } + log.Print("Preparing add_subscription statement.") + add_subscription_stmt, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)") + if err != nil { + return err + } + log.Print("Preparing create_forum statement.") create_forum_stmt, err = db.Prepare("INSERT INTO `forums`(`name`,`desc`,`active`,`preset`) VALUES (?,?,?,?)") if err != nil { @@ -571,6 +624,30 @@ func gen_mysql() (err error) { if err != nil { return err } + + log.Print("Preparing delete_reply statement.") + delete_reply_stmt, err = db.Prepare("DELETE FROM `replies` WHERE `rid` = ?") + if err != nil { + return err + } + + log.Print("Preparing delete_topic statement.") + delete_topic_stmt, err = db.Prepare("DELETE FROM `topics` WHERE `tid` = ?") + if err != nil { + return err + } + + log.Print("Preparing delete_profile_reply statement.") + delete_profile_reply_stmt, err = db.Prepare("DELETE FROM `users_replies` WHERE `rid` = ?") + if err != nil { + return err + } + + log.Print("Preparing delete_forum_perms_by_forum statement.") + delete_forum_perms_by_forum_stmt, err = db.Prepare("DELETE FROM `forums_permissions` WHERE `fid` = ?") + if err != nil { + return err + } return nil } diff --git a/main.go b/main.go index 7884b24b..94abe137 100644 --- a/main.go +++ b/main.go @@ -61,7 +61,7 @@ func compile_templates() { log.Print("Compiling the templates") - topic := TopicUser{1,"Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"classname","",default_group,"",no_css_tmpl,0,"","","","",58,false} + topic := TopicUser{1,"Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"classname","","",default_group,"",no_css_tmpl,0,"","","","",58,false} var replyList []Reply replyList = append(replyList, Reply{0,0,"","Yo!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1,"",""}) @@ -90,7 +90,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,"classname","Admin",default_group,"","",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,"classname","","Admin",default_group,"","",0,"","","","",58,false}) forum_item := Forum{1,"General Forum","Where the general stuff happens",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) diff --git a/mod_routes.go b/mod_routes.go index 861ac122..89909868 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -22,15 +22,14 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) { is_js = "0" } - var tid, fid int + var tid int tid, err = strconv.Atoi(r.URL.Path[len("/topic/edit/submit/"):]) if err != nil { PreErrorJSQ("The provided TopicID is not a valid number.",w,r,is_js) return } - var old_is_closed bool - err = db.QueryRow("select parentID, is_closed from topics where tid = ?", tid).Scan(&fid,&old_is_closed) + old_topic, err := topics.CascadeGet(tid) if err == sql.ErrNoRows { PreErrorJSQ("The topic you tried to edit doesn't exist.",w,r,is_js) return @@ -39,7 +38,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) { return } - user, ok := SimpleForumSessionCheck(w,r,fid) + user, ok := SimpleForumSessionCheck(w,r,old_topic.ParentID) if !ok { return } @@ -65,7 +64,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) { return } - if old_is_closed != is_closed { + if old_topic.Is_Closed != is_closed { var action string if is_closed { action = "lock" @@ -97,9 +96,12 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) { } err = topics.Load(tid) - if err != nil { + if err == sql.ErrNoRows { LocalErrorJSQ("This topic no longer exists!",w,r,user,is_js) return + } else if err != nil { + InternalErrorJSQ(err,w,r,is_js) + return } if is_js == "0" { @@ -116,9 +118,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) { return } - var content string - var createdBy, fid int - err = db.QueryRow("select content, createdBy, parentID from topics where tid = ?", tid).Scan(&content, &createdBy, &fid) + topic, err := topics.CascadeGet(tid) if err == sql.ErrNoRows { PreError("The topic you tried to delete doesn't exist.",w,r) return @@ -127,7 +127,7 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) { return } - user, ok := SimpleForumSessionCheck(w,r,fid) + user, ok := SimpleForumSessionCheck(w,r,topic.ParentID) if !ok { return } @@ -163,20 +163,20 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) { //log.Print("Topic #" + strconv.Itoa(tid) + " was deleted by User #" + strconv.Itoa(user.ID)) http.Redirect(w,r,"/",http.StatusSeeOther) - wcount := word_count(content) - err = decrease_post_user_stats(wcount,createdBy,true,user) + wcount := word_count(topic.Content) + err = decrease_post_user_stats(wcount,topic.CreatedBy,true,user) if err != nil { InternalError(err,w,r) return } - _, err = remove_topics_from_forum_stmt.Exec(1, fid) + _, err = remove_topics_from_forum_stmt.Exec(1, topic.ParentID) if err != nil { InternalError(err,w,r) return } - forums[fid].TopicCount -= 1 + forums[topic.ParentID].TopicCount -= 1 topics.Remove(tid) } @@ -553,7 +553,7 @@ func route_ban(w http.ResponseWriter, r *http.Request) { } var uname string - err = db.QueryRow("select name from users where uid = ?", uid).Scan(&uname) + err = get_user_name_stmt.QueryRow(uid).Scan(&uname) if err == sql.ErrNoRows { LocalError("The user you're trying to ban no longer exists.",w,r,user) return @@ -588,14 +588,14 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) { LocalError("The provided User ID is not a valid number.",w,r,user) return } - if uid == -2 { + /*if uid == -2 { LocalError("Sigh, are you really trying to ban me? Do you despise so much? Despite all of our adventures over at /arcane-tower/...?",w,r,user) return - } + }*/ var group int var is_super_admin bool - err = db.QueryRow("select `group`,`is_super_admin` from `users` where `uid` = ?", uid).Scan(&group, &is_super_admin) + err = get_user_rank_stmt.QueryRow(uid).Scan(&group, &is_super_admin) if err == sql.ErrNoRows { LocalError("The user you're trying to ban no longer exists.",w,r,user) return @@ -663,9 +663,8 @@ func route_unban(w http.ResponseWriter, r *http.Request) { return } - var uname string var group int - err = db.QueryRow("select `name`, `group` from users where `uid` = ?", uid).Scan(&uname, &group) + err = get_user_group_stmt.QueryRow(uid).Scan(&group) if err == sql.ErrNoRows { LocalError("The user you're trying to unban no longer exists.",w,r,user) return @@ -697,10 +696,14 @@ func route_unban(w http.ResponseWriter, r *http.Request) { } err = users.Load(uid) - if err != nil { + if err != nil && err == sql.ErrNoRows { LocalError("This user no longer exists!",w,r,user) return + } else if err != nil { + InternalError(err,w,r) + return } + http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther) } @@ -724,9 +727,8 @@ func route_activate(w http.ResponseWriter, r *http.Request) { return } - var uname string var active bool - err = db.QueryRow("select `name`,`active` from users where `uid` = ?", uid).Scan(&uname, &active) + err = get_user_active_stmt.QueryRow(uid).Scan(&active) if err == sql.ErrNoRows { LocalError("The account you're trying to activate no longer exists.",w,r,user) return diff --git a/mysql.go b/mysql.go index b66a9829..c2d879fd 100644 --- a/mysql.go +++ b/mysql.go @@ -16,19 +16,12 @@ var db_collation string = "utf8mb4_general_ci" var get_topic_replies_offset_stmt *sql.Stmt // I'll need to rewrite this one to stop it hard-coding the per page setting before moving it to the query generator var get_forum_topics_offset_stmt *sql.Stmt var notify_watchers_stmt *sql.Stmt -var add_subscription_stmt *sql.Stmt -var delete_reply_stmt *sql.Stmt -var delete_topic_stmt *sql.Stmt var get_activity_feed_by_watcher_stmt *sql.Stmt var get_activity_count_by_watcher_stmt *sql.Stmt -var add_email_stmt *sql.Stmt -var update_email_stmt *sql.Stmt -var verify_email_stmt *sql.Stmt -var delete_profile_reply_stmt *sql.Stmt +var update_email_stmt, verify_email_stmt *sql.Stmt var forum_entry_exists_stmt *sql.Stmt var group_entry_exists_stmt *sql.Stmt -var delete_forum_perms_by_forum_stmt *sql.Stmt var add_forum_perms_to_forum_admins_stmt *sql.Stmt var add_forum_perms_to_forum_staff_stmt *sql.Stmt var add_forum_perms_to_forum_members_stmt *sql.Stmt @@ -86,24 +79,6 @@ func init_database() (err error) { return err } - log.Print("Preparing add_subscription statement.") - add_subscription_stmt, err = db.Prepare("INSERT INTO activity_subscriptions(user,targetID,targetType,level) VALUES(?,?,?,2)") - if err != nil { - return err - } - - log.Print("Preparing delete_reply statement.") - delete_reply_stmt, err = db.Prepare("DELETE FROM replies WHERE rid = ?") - if err != nil { - return err - } - - log.Print("Preparing delete_topic statement.") - delete_topic_stmt, err = db.Prepare("DELETE FROM topics WHERE tid = ?") - if err != nil { - return err - } - log.Print("Preparing get_activity_feed_by_watcher statement.") get_activity_feed_by_watcher_stmt, err = db.Prepare("SELECT activity_stream_matches.asid, activity_stream.actor, activity_stream.targetUser, activity_stream.event, activity_stream.elementType, activity_stream.elementID FROM `activity_stream_matches` INNER JOIN `activity_stream` ON activity_stream_matches.asid = activity_stream.asid AND activity_stream_matches.watcher != activity_stream.actor WHERE `watcher` = ? ORDER BY activity_stream.asid ASC LIMIT 8") if err != nil { @@ -116,12 +91,6 @@ func init_database() (err error) { return err } - log.Print("Preparing add_email statement.") - add_email_stmt, err = db.Prepare("INSERT INTO emails(`email`,`uid`,`validated`,`token`) VALUES(?,?,?,?)") - if err != nil { - return err - } - log.Print("Preparing update_email statement.") update_email_stmt, err = db.Prepare("UPDATE emails SET email = ?, uid = ?, validated = ?, token = ? WHERE email = ?") if err != nil { @@ -134,12 +103,6 @@ func init_database() (err error) { return err } - log.Print("Preparing delete_profile_reply statement.") - delete_profile_reply_stmt, err = db.Prepare("DELETE FROM users_replies WHERE rid = ?") - if err != nil { - return err - } - log.Print("Preparing forum_entry_exists statement.") forum_entry_exists_stmt, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' order by fid asc limit 1") if err != nil { @@ -152,12 +115,6 @@ func init_database() (err error) { return err } - log.Print("Preparing delete_forum_perms_by_forum statement.") - delete_forum_perms_by_forum_stmt, err = db.Prepare("DELETE FROM forums_permissions WHERE fid = ?") - if err != nil { - return err - } - log.Print("Preparing add_forum_perms_to_forum_admins statement.") add_forum_perms_to_forum_admins_stmt, err = db.Prepare("INSERT INTO forums_permissions(gid,fid,preset,permissions) SELECT `gid`,? AS fid,? AS preset,? AS permissions FROM users_groups WHERE is_admin = 1") if err != nil { diff --git a/no_websockets.go b/no_websockets.go index 12a90e7a..0271ca92 100644 --- a/no_websockets.go +++ b/no_websockets.go @@ -32,5 +32,9 @@ func(hub *WS_Hub) push_alert(_ int, _ string, _ string, _ int, _ int, _ int) err return ws_nouser } +func(hub *WS_Hub) push_alerts(_ []int, _ string, _ string, _ int, _ int, _ int) error { + return ws_nouser +} + func route_websockets(_ http.ResponseWriter, _ *http.Request) { } diff --git a/panel_routes.go b/panel_routes.go index b589bc84..64282749 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -1030,7 +1030,6 @@ func route_panel_groups(w http.ResponseWriter, r *http.Request){ } can_edit = user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod) - groupList = append(groupList, GroupAdmin{group.ID,group.Name,rank,rank_class,can_edit,can_delete}) } //fmt.Printf("%+v\n", groupList) diff --git a/public/global.js b/public/global.js index 221b26b1..5f2e5623 100644 --- a/public/global.js +++ b/public/global.js @@ -1,6 +1,7 @@ var form_vars = {}; var alertList = []; var alertCount = 0; +var conn; function post_link(event) { @@ -345,7 +346,7 @@ $(document).ready(function(){ $(".menu_alerts").click(function(event) { event.stopPropagation(); if($(this).hasClass("selectedAlert")) return; - load_alerts($(this)); + if(!conn) load_alerts($(this)); this.className += " selectedAlert"; document.getElementById("back").className += " alertActive" }); diff --git a/query_gen/main.go b/query_gen/main.go index 1e99a41c..bd1ea2b5 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -1,10 +1,7 @@ /* WIP Under Construction */ package main -//import "fmt" -import "strings" import "log" -import "os" var db_registry []DB_Adapter var blank_order []DB_Order @@ -40,6 +37,7 @@ type DB_Joiner struct LeftColumn string RightTable string RightColumn string + Operator string } type DB_Order struct @@ -63,9 +61,13 @@ type DB_Adapter interface { simple_insert(string,string,string,string) error simple_replace(string,string,string,string) error simple_update(string,string,string,string) error + simple_delete(string,string,string) error + purge(string,string) error simple_select(string,string,string,string,string/*,int,int*/) error simple_left_join(string,string,string,string,string,string,string/*,int,int*/) error + simple_inner_join(string,string,string,string,string,string,string/*,int,int*/) error write() error + // TO-DO: Add a simple query builder } @@ -83,7 +85,11 @@ func write_statements(adapter DB_Adapter) error { if err != nil { return err } - err = write_joins(adapter) + err = write_left_joins(adapter) + if err != nil { + return err + } + err = write_inner_joins(adapter) if err != nil { return err } @@ -99,6 +105,10 @@ func write_statements(adapter DB_Adapter) error { if err != nil { return err } + err = write_deletes(adapter) + if err != nil { + return err + } return nil } @@ -108,7 +118,7 @@ func write_selects(adapter DB_Adapter) error { adapter.simple_select("get_full_user","users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","") - adapter.simple_select("get_topic","topics","title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount","tid = ?","") + adapter.simple_select("get_topic","topics","title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data","tid = ?","") adapter.simple_select("get_reply","replies","content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount","rid = ?","") @@ -145,24 +155,40 @@ func write_selects(adapter DB_Adapter) error { adapter.simple_select("get_user_name","users","name","uid = ?","") + adapter.simple_select("get_user_rank","users","group, is_super_admin","uid = ?","") + + adapter.simple_select("get_user_active","users","active","uid = ?","") + + adapter.simple_select("get_user_group","users","group","uid = ?","") + adapter.simple_select("get_emails_by_user","emails","email, validated","uid = ?","") adapter.simple_select("get_topic_basic","topics","title, content","tid = ?","") + + adapter.simple_select("get_activity_entry","activity_stream","actor, targetUser, event, elementType, elementID","asid = ?","") + return nil } -func write_joins(adapter DB_Adapter) error { +func write_left_joins(adapter DB_Adapter) error { adapter.simple_left_join("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.simple_left_join("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.simple_left_join("get_topic_by_reply","replies","topics","topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount","replies.tid = topics.tid","rid = ?","") + adapter.simple_left_join("get_topic_by_reply","replies","topics","topics.tid, topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, topics.data","replies.tid = topics.tid","rid = ?","") adapter.simple_left_join("get_topic_replies","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.createdBy = users.uid","tid = ?","") adapter.simple_left_join("get_forum_topics","topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, users.name, users.avatar","topics.createdBy = users.uid","topics.parentID = ?","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc") adapter.simple_left_join("get_profile_replies","users_replies","users","users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group","users_replies.createdBy = users.uid","users_replies.uid = ?","") + + return nil +} + +func write_inner_joins(adapter DB_Adapter) error { + adapter.simple_inner_join("get_watchers","activity_stream","activity_subscriptions","activity_subscriptions.user","activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor","asid = ?","") + return nil } @@ -185,8 +211,12 @@ func write_inserts(adapter DB_Adapter) error { // create_account_stmt, err = db.Prepare("INSERT INTO adapter.simple_insert("register","users","name, email, password, salt, group, is_super_admin, session, active, message","?,?,?,?,?,0,?,?,''") + adapter.simple_insert("add_email","emails","email, uid, validated, token","?,?,?,?") + adapter.simple_insert("create_profile_reply","users_replies","uid,content,parsed_content,createdAt,createdBy","?,?,?,NOW(),?") + adapter.simple_insert("add_subscription","activity_subscriptions","user,targetID,targetType,level","?,?,?,2") + adapter.simple_insert("create_forum","forums","name, desc, active, preset","?,?,?,?") adapter.simple_insert("add_forum_perms_to_forum","forums_permissions","gid,fid,preset,permissions","?,?,?,?") @@ -286,335 +316,15 @@ func write_updates(adapter DB_Adapter) error { return nil } -func _process_columns(colstr string) (columns []DB_Column) { - if colstr == "" { - return columns - } - colstr = strings.Replace(colstr," as "," AS ",-1) - for _, segment := range strings.Split(colstr,",") { - var outcol DB_Column - dothalves := strings.Split(strings.TrimSpace(segment),".") - - var halves []string - if len(dothalves) == 2 { - outcol.Table = dothalves[0] - halves = strings.Split(dothalves[1]," AS ") - } else { - halves = strings.Split(dothalves[0]," AS ") - } - - halves[0] = strings.TrimSpace(halves[0]) - if len(halves) == 2 { - outcol.Alias = strings.TrimSpace(halves[1]) - } - if halves[0][len(halves[0]) - 1] == ')' { - outcol.Type = "function" - } else { - outcol.Type = "column" - } - - outcol.Left = halves[0] - columns = append(columns,outcol) - } - return columns -} - -func _process_orderby(orderstr string) (order []DB_Order) { - if orderstr == "" { - return order - } - for _, segment := range strings.Split(orderstr,",") { - var outorder DB_Order - halves := strings.Split(strings.TrimSpace(segment)," ") - if len(halves) != 2 { - continue - } - outorder.Column = halves[0] - outorder.Order = strings.ToLower(halves[1]) - order = append(order,outorder) - } - return order -} - -func _process_joiner(joinstr string) (joiner []DB_Joiner) { - if joinstr == "" { - return joiner - } - joinstr = strings.Replace(joinstr," on "," ON ",-1) - joinstr = strings.Replace(joinstr," and "," AND ",-1) - for _, segment := range strings.Split(joinstr," AND ") { - var outjoin DB_Joiner - halves := strings.Split(segment,"=") - if len(halves) != 2 { - continue - } - - left_column := strings.Split(halves[0],".") - right_column := strings.Split(halves[1],".") - outjoin.LeftTable = strings.TrimSpace(left_column[0]) - outjoin.RightTable = strings.TrimSpace(right_column[0]) - outjoin.LeftColumn = strings.TrimSpace(left_column[1]) - outjoin.RightColumn = strings.TrimSpace(right_column[1]) - - joiner = append(joiner,outjoin) - } - return joiner -} - -// TO-DO: Add support for keywords like BETWEEN. We'll probably need an arbitrary expression parser like with the update setters. -func _process_where(wherestr string) (where []DB_Where) { - if wherestr == "" { - return where - } - wherestr = strings.Replace(wherestr," and "," AND ",-1) - for _, segment := range strings.Split(wherestr," AND ") { - // TO-DO: Subparse the contents of a function and spit out a DB_Function struct - var outwhere DB_Where - var parseOffset int - var left, right string - - - left, parseOffset = _get_identifier(segment, parseOffset) - outwhere.Operator, parseOffset = _get_operator(segment, parseOffset + 1) - right, parseOffset = _get_identifier(segment, parseOffset + 1) - outwhere.LeftType = _get_identifier_type(left) - outwhere.RightType = _get_identifier_type(right) - - left_operand := strings.Split(left,".") - right_operand := strings.Split(right,".") - - if len(left_operand) == 2 { - outwhere.LeftTable = strings.TrimSpace(left_operand[0]) - outwhere.LeftColumn = strings.TrimSpace(left_operand[1]) - } else { - outwhere.LeftColumn = strings.TrimSpace(left_operand[0]) - } - - if len(right_operand) == 2 { - outwhere.RightTable = strings.TrimSpace(right_operand[0]) - outwhere.RightColumn = strings.TrimSpace(right_operand[1]) - } else { - outwhere.RightColumn = strings.TrimSpace(right_operand[0]) - } - - where = append(where,outwhere) - } - return where -} - -func _process_set(setstr string) (setter []DB_Setter) { - if setstr == "" { - return setter - } - //fmt.Println("setstr",setstr) +func write_deletes(adapter DB_Adapter) error { + adapter.simple_delete("delete_reply","replies","rid = ?") - // First pass, splitting the string by commas while ignoring the innards of functions - var setset []string - var buffer string - var last_item int - setstr += "," - for i := 0; i < len(setstr); i++ { - if setstr[i] == '(' { - i = _skip_function_call(setstr,i-1) - setset = append(setset,setstr[last_item:i+1]) - buffer = "" - last_item = i + 2 - } else if setstr[i] == ',' && buffer != "" { - setset = append(setset,buffer) - buffer = "" - last_item = i + 1 - } else if (setstr[i] > 32) && setstr[i] != ',' && setstr[i] != ')' { - buffer += string(setstr[i]) - } - } + adapter.simple_delete("delete_topic","topics","tid = ?") - // Second pass. Break this setitem into manageable chunks - buffer = "" - for _, setitem := range setset { - var tmp_setter DB_Setter - halves := strings.Split(setitem,"=") - if len(halves) != 2 { - continue - } - tmp_setter.Column = strings.TrimSpace(halves[0]) - - halves[1] += ")" - var optype int // 0: None, 1: Number, 2: Column, 3: Function, 4: String, 5: Operator - //fmt.Println("halves[1]",halves[1]) - for i := 0; i < len(halves[1]); i++ { - char := halves[1][i] - //fmt.Println("optype",optype) - switch(optype) { - case 0: // unknown - if ('0' <= char && char <= '9') { - optype = 1 - buffer = string(char) - } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') { - optype = 2 - buffer = string(char) - } else if char == '\'' { - optype = 4 - } else if _is_op_byte(char) { - optype = 5 - buffer = string(char) - } else if char == '?' { - //fmt.Println("Expr:","?") - tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{"?","substitute"}) - } - case 1: // number - if ('0' <= char && char <= '9') { - buffer += string(char) - } else { - optype = 0 - i-- - //fmt.Println("Expr:",buffer) - tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"number"}) - } - case 2: // column - if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') { - buffer += string(char) - } else if char == '(' { - optype = 3 - i-- - } else { - optype = 0 - i-- - //fmt.Println("Expr:",buffer) - tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"column"}) - } - case 3: // function - var pre_i int = i - //fmt.Println("buffer",buffer) - //fmt.Println("len(halves)",len(halves[1])) - //fmt.Println("pre_i",string(halves[1][pre_i])) - //fmt.Println("msg prior to pre_i",halves[1][0:pre_i]) - i = _skip_function_call(halves[1],i-1) - //fmt.Println("i",i) - //fmt.Println("msg prior to i-1",halves[1][0:i-1]) - //fmt.Println("string(i-1)",string(halves[1][i-1])) - //fmt.Println("string(i)",string(halves[1][i])) - buffer += halves[1][pre_i:i] + string(halves[1][i]) - //fmt.Println("Expr:",buffer) - tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"function"}) - optype = 0 - case 4: // string - if char != '\'' { - buffer += string(char) - } else { - optype = 0 - //fmt.Println("Expr:",buffer) - tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"string"}) - } - case 5: // operator - if _is_op_byte(char) { - buffer += string(char) - } else { - optype = 0 - i-- - //fmt.Println("Expr:",buffer) - tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"operator"}) - } - } - } - setter = append(setter,tmp_setter) - } - //fmt.Println("setter",setter) - return setter -} - -func _is_op_byte(char byte) bool { - return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' -} - -func _process_fields(fieldstr string) (fields []DB_Field) { - if fieldstr == "" { - return fields - } - var buffer string - var last_item int - fieldstr += "," - for i := 0; i < len(fieldstr); i++ { - if fieldstr[i] == '(' { - i = _skip_function_call(fieldstr,i-1) - fields = append(fields,DB_Field{Name:fieldstr[last_item:i+1],Type:_get_identifier_type(fieldstr[last_item:i+1])}) - buffer = "" - last_item = i + 2 - } else if fieldstr[i] == ',' && buffer != "" { - fields = append(fields,DB_Field{Name:buffer,Type:_get_identifier_type(buffer)}) - buffer = "" - last_item = i + 1 - } else if (fieldstr[i] > 32) && fieldstr[i] != ',' && fieldstr[i] != ')' { - buffer += string(fieldstr[i]) - } - } - return fields -} - -func _get_identifier_type(identifier string) string { - if ('a' <= identifier[0] && identifier[0] <= 'z') || ('A' <= identifier[0] && identifier[0] <= 'Z') { - if identifier[len(identifier) - 1] == ')' { - return "function" - } - return "column" - } - if identifier[0] == '\'' || identifier[0] == '"' { - return "string" - } - return "literal" -} - -func _get_identifier(segment string, startOffset int) (out string, i int) { - segment = strings.TrimSpace(segment) - segment += " " // Avoid overflow bugs with slicing - for i = startOffset; i < len(segment); i++ { - if segment[i] == '(' { - i = _skip_function_call(segment,i) - return strings.TrimSpace(segment[startOffset:i]), (i - 1) - } - if segment[i] == ' ' && i != startOffset { - return strings.TrimSpace(segment[startOffset:i]), (i - 1) - } - } - return strings.TrimSpace(segment[startOffset:]), (i - 1) -} - -func _get_operator(segment string, startOffset int) (out string, i int) { - segment = strings.TrimSpace(segment) - segment += " " // Avoid overflow bugs with slicing - for i = startOffset; i < len(segment); i++ { - if segment[i] == ' ' && i != startOffset { - return strings.TrimSpace(segment[startOffset:i]), (i - 1) - } - } - return strings.TrimSpace(segment[startOffset:]), (i - 1) -} - -func _skip_function_call(data string, index int) int { - var brace_count int - for ;index < len(data); index++{ - char := data[index] - if char == '(' { - brace_count++ - } else if char == ')' { - brace_count-- - if brace_count == 0 { - return index - } - } - } - return index -} - -func write_file(name string, content string) (err error) { - f, err := os.Create(name) - if err != nil { - return err - } - _, err = f.WriteString(content) - if err != nil { - return err - } - f.Sync() - f.Close() - return -} + + adapter.simple_delete("delete_profile_reply","users_replies","rid = ?") + + adapter.simple_delete("delete_forum_perms_by_forum","forums_permissions","fid = ?") + + return nil +} \ No newline at end of file diff --git a/query_gen/mysql.go b/query_gen/mysql.go index f62b7111..0f83ae7b 100644 --- a/query_gen/mysql.go +++ b/query_gen/mysql.go @@ -152,6 +152,53 @@ func (adapter *Mysql_Adapter) simple_update(name string, table string, set strin return nil } +func (adapter *Mysql_Adapter) simple_delete(name string, table string, where string) error { + if name == "" { + return errors.New("You need a name for this statement") + } + if table == "" { + return errors.New("You need a name for this table") + } + if where == "" { + return errors.New("You need to specify what data you want to delete") + } + + var querystr string = "DELETE FROM `" + table + "` WHERE" + for _, loc := range _process_where(where) { + var left, right string + + if loc.LeftType == "column" { + left = "`" + loc.LeftColumn + "`" + } else { + left = loc.LeftColumn + } + + if loc.RightType == "column" { + right = "`" + loc.RightColumn + "`" + } else { + right = loc.RightColumn + } + + querystr += " " + left + " " + loc.Operator + " " + right + " AND " + } + querystr = querystr[0:len(querystr) - 4] + + adapter.write_statement(name,strings.TrimSpace(querystr)) + return nil +} + +// We don't want to accidentally wipe tables, so we'll have a seperate method for purging tables instead +func (adapter *Mysql_Adapter) purge(name string, table string) error { + if name == "" { + return errors.New("You need a name for this statement") + } + if table == "" { + return errors.New("You need a name for this table") + } + adapter.write_statement(name,"DELETE FROM `" + table + "`") + return nil +} + func (adapter *Mysql_Adapter) simple_select(name string, table string, columns string, where string, orderby string/*, offset int, maxCount int*/) error { if name == "" { return errors.New("You need a name for this statement") @@ -253,7 +300,92 @@ func (adapter *Mysql_Adapter) simple_left_join(name string, table1 string, table querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON " for _, joiner := range _process_joiner(joiners) { - querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "`=`" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " + querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " + } + // Remove the trailing AND + querystr = querystr[0:len(querystr) - 4] + + if len(where) != 0 { + querystr += " WHERE" + for _, loc := range _process_where(where) { + var left, right string + + if loc.LeftTable != "" { + left = "`" + loc.LeftTable + "`.`" + loc.LeftColumn + "`" + } else if loc.LeftType == "column" { + left = "`" + loc.LeftColumn + "`" + } else { + left = loc.LeftColumn + } + + if loc.RightTable != "" { + right = "`" + loc.RightTable + "`.`" + loc.RightColumn + "`" + } else if loc.RightType == "column" { + right = "`" + loc.RightColumn + "`" + } else { + right = loc.RightColumn + } + + querystr += " " + left + " " + loc.Operator + " " + right + " AND " + } + querystr = querystr[0:len(querystr) - 4] + } + + if len(orderby) != 0 { + querystr += " ORDER BY " + for _, column := range _process_orderby(orderby) { + querystr += column.Column + " " + strings.ToUpper(column.Order) + "," + } + querystr = querystr[0:len(querystr) - 1] + } + + adapter.write_statement(name,strings.TrimSpace(querystr)) + return nil +} + +func (adapter *Mysql_Adapter) simple_inner_join(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string/*, offset int, maxCount int*/) error { + if name == "" { + return errors.New("You need a name for this statement") + } + if table1 == "" { + return errors.New("You need a name for the left table") + } + if table2 == "" { + return errors.New("You need a name for the right table") + } + if len(columns) == 0 { + return errors.New("No columns found for simple_inner_join") + } + if len(joiners) == 0 { + return errors.New("No joiners found for simple_inner_join") + } + + var querystr string = "SELECT " + + for _, column := range _process_columns(columns) { + var source, alias string + + // Escape the column names, just in case we've used a reserved keyword + if column.Table != "" { + source = "`" + column.Table + "`.`" + column.Left + "`" + } else if column.Type == "function" { + source = column.Left + } else { + source = "`" + column.Left + "`" + } + + if column.Alias != "" { + alias = " AS `" + column.Alias + "`" + } + querystr += source + alias + "," + } + + // Remove the trailing comma + querystr = querystr[0:len(querystr) - 1] + + querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON " + for _, joiner := range _process_joiner(joiners) { + querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " } // Remove the trailing AND querystr = querystr[0:len(querystr) - 4] diff --git a/query_gen/utils.go b/query_gen/utils.go new file mode 100644 index 00000000..18434459 --- /dev/null +++ b/query_gen/utils.go @@ -0,0 +1,341 @@ +/* WIP Under Construction */ +package main + +//import "fmt" +import "strings" +import "os" + +func _process_columns(colstr string) (columns []DB_Column) { + if colstr == "" { + return columns + } + colstr = strings.Replace(colstr," as "," AS ",-1) + for _, segment := range strings.Split(colstr,",") { + var outcol DB_Column + dothalves := strings.Split(strings.TrimSpace(segment),".") + + var halves []string + if len(dothalves) == 2 { + outcol.Table = dothalves[0] + halves = strings.Split(dothalves[1]," AS ") + } else { + halves = strings.Split(dothalves[0]," AS ") + } + + halves[0] = strings.TrimSpace(halves[0]) + if len(halves) == 2 { + outcol.Alias = strings.TrimSpace(halves[1]) + } + if halves[0][len(halves[0]) - 1] == ')' { + outcol.Type = "function" + } else { + outcol.Type = "column" + } + + outcol.Left = halves[0] + columns = append(columns,outcol) + } + return columns +} + +func _process_orderby(orderstr string) (order []DB_Order) { + if orderstr == "" { + return order + } + for _, segment := range strings.Split(orderstr,",") { + var outorder DB_Order + halves := strings.Split(strings.TrimSpace(segment)," ") + if len(halves) != 2 { + continue + } + outorder.Column = halves[0] + outorder.Order = strings.ToLower(halves[1]) + order = append(order,outorder) + } + return order +} + +func _process_joiner(joinstr string) (joiner []DB_Joiner) { + if joinstr == "" { + return joiner + } + joinstr = strings.Replace(joinstr," on "," ON ",-1) + joinstr = strings.Replace(joinstr," and "," AND ",-1) + for _, segment := range strings.Split(joinstr," AND ") { + var outjoin DB_Joiner + var parseOffset int + var left, right string + + left, parseOffset = _get_identifier(segment, parseOffset) + outjoin.Operator, parseOffset = _get_operator(segment, parseOffset + 1) + right, parseOffset = _get_identifier(segment, parseOffset + 1) + + + left_column := strings.Split(left,".") + right_column := strings.Split(right,".") + outjoin.LeftTable = strings.TrimSpace(left_column[0]) + outjoin.RightTable = strings.TrimSpace(right_column[0]) + outjoin.LeftColumn = strings.TrimSpace(left_column[1]) + outjoin.RightColumn = strings.TrimSpace(right_column[1]) + + joiner = append(joiner,outjoin) + } + return joiner +} + +// TO-DO: Add support for keywords like BETWEEN. We'll probably need an arbitrary expression parser like with the update setters. +func _process_where(wherestr string) (where []DB_Where) { + if wherestr == "" { + return where + } + wherestr = strings.Replace(wherestr," and "," AND ",-1) + for _, segment := range strings.Split(wherestr," AND ") { + // TO-DO: Subparse the contents of a function and spit out a DB_Function struct + var outwhere DB_Where + var parseOffset int + var left, right string + + left, parseOffset = _get_identifier(segment, parseOffset) + outwhere.Operator, parseOffset = _get_operator(segment, parseOffset + 1) + right, parseOffset = _get_identifier(segment, parseOffset + 1) + outwhere.LeftType = _get_identifier_type(left) + outwhere.RightType = _get_identifier_type(right) + + left_operand := strings.Split(left,".") + right_operand := strings.Split(right,".") + + if len(left_operand) == 2 { + outwhere.LeftTable = strings.TrimSpace(left_operand[0]) + outwhere.LeftColumn = strings.TrimSpace(left_operand[1]) + } else { + outwhere.LeftColumn = strings.TrimSpace(left_operand[0]) + } + + if len(right_operand) == 2 { + outwhere.RightTable = strings.TrimSpace(right_operand[0]) + outwhere.RightColumn = strings.TrimSpace(right_operand[1]) + } else { + outwhere.RightColumn = strings.TrimSpace(right_operand[0]) + } + + where = append(where,outwhere) + } + return where +} + +func _process_set(setstr string) (setter []DB_Setter) { + if setstr == "" { + return setter + } + //fmt.Println("setstr",setstr) + + // First pass, splitting the string by commas while ignoring the innards of functions + var setset []string + var buffer string + var last_item int + setstr += "," + for i := 0; i < len(setstr); i++ { + if setstr[i] == '(' { + i = _skip_function_call(setstr,i-1) + setset = append(setset,setstr[last_item:i+1]) + buffer = "" + last_item = i + 2 + } else if setstr[i] == ',' && buffer != "" { + setset = append(setset,buffer) + buffer = "" + last_item = i + 1 + } else if (setstr[i] > 32) && setstr[i] != ',' && setstr[i] != ')' { + buffer += string(setstr[i]) + } + } + + // Second pass. Break this setitem into manageable chunks + buffer = "" + for _, setitem := range setset { + var tmp_setter DB_Setter + halves := strings.Split(setitem,"=") + if len(halves) != 2 { + continue + } + tmp_setter.Column = strings.TrimSpace(halves[0]) + + halves[1] += ")" + var optype int // 0: None, 1: Number, 2: Column, 3: Function, 4: String, 5: Operator + //fmt.Println("halves[1]",halves[1]) + for i := 0; i < len(halves[1]); i++ { + char := halves[1][i] + //fmt.Println("optype",optype) + switch(optype) { + case 0: // unknown + if ('0' <= char && char <= '9') { + optype = 1 + buffer = string(char) + } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') { + optype = 2 + buffer = string(char) + } else if char == '\'' { + optype = 4 + } else if _is_op_byte(char) { + optype = 5 + buffer = string(char) + } else if char == '?' { + //fmt.Println("Expr:","?") + tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{"?","substitute"}) + } + case 1: // number + if ('0' <= char && char <= '9') { + buffer += string(char) + } else { + optype = 0 + i-- + //fmt.Println("Expr:",buffer) + tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"number"}) + } + case 2: // column + if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') { + buffer += string(char) + } else if char == '(' { + optype = 3 + i-- + } else { + optype = 0 + i-- + //fmt.Println("Expr:",buffer) + tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"column"}) + } + case 3: // function + var pre_i int = i + //fmt.Println("buffer",buffer) + //fmt.Println("len(halves)",len(halves[1])) + //fmt.Println("pre_i",string(halves[1][pre_i])) + //fmt.Println("msg prior to pre_i",halves[1][0:pre_i]) + i = _skip_function_call(halves[1],i-1) + //fmt.Println("i",i) + //fmt.Println("msg prior to i-1",halves[1][0:i-1]) + //fmt.Println("string(i-1)",string(halves[1][i-1])) + //fmt.Println("string(i)",string(halves[1][i])) + buffer += halves[1][pre_i:i] + string(halves[1][i]) + //fmt.Println("Expr:",buffer) + tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"function"}) + optype = 0 + case 4: // string + if char != '\'' { + buffer += string(char) + } else { + optype = 0 + //fmt.Println("Expr:",buffer) + tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"string"}) + } + case 5: // operator + if _is_op_byte(char) { + buffer += string(char) + } else { + optype = 0 + i-- + //fmt.Println("Expr:",buffer) + tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"operator"}) + } + } + } + setter = append(setter,tmp_setter) + } + //fmt.Println("setter",setter) + return setter +} + +func _is_op_byte(char byte) bool { + return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' +} + +func _process_fields(fieldstr string) (fields []DB_Field) { + if fieldstr == "" { + return fields + } + var buffer string + var last_item int + fieldstr += "," + for i := 0; i < len(fieldstr); i++ { + if fieldstr[i] == '(' { + i = _skip_function_call(fieldstr,i-1) + fields = append(fields,DB_Field{Name:fieldstr[last_item:i+1],Type:_get_identifier_type(fieldstr[last_item:i+1])}) + buffer = "" + last_item = i + 2 + } else if fieldstr[i] == ',' && buffer != "" { + fields = append(fields,DB_Field{Name:buffer,Type:_get_identifier_type(buffer)}) + buffer = "" + last_item = i + 1 + } else if (fieldstr[i] > 32) && fieldstr[i] != ',' && fieldstr[i] != ')' { + buffer += string(fieldstr[i]) + } + } + return fields +} + +func _get_identifier_type(identifier string) string { + if ('a' <= identifier[0] && identifier[0] <= 'z') || ('A' <= identifier[0] && identifier[0] <= 'Z') { + if identifier[len(identifier) - 1] == ')' { + return "function" + } + return "column" + } + if identifier[0] == '\'' || identifier[0] == '"' { + return "string" + } + return "literal" +} + +func _get_identifier(segment string, startOffset int) (out string, i int) { + segment = strings.TrimSpace(segment) + segment += " " // Avoid overflow bugs with slicing + for i = startOffset; i < len(segment); i++ { + if segment[i] == '(' { + i = _skip_function_call(segment,i) + return strings.TrimSpace(segment[startOffset:i]), (i - 1) + } + if (segment[i] == ' ' || _is_op_byte(segment[i])) && i != startOffset { + return strings.TrimSpace(segment[startOffset:i]), (i - 1) + } + } + return strings.TrimSpace(segment[startOffset:]), (i - 1) +} + +func _get_operator(segment string, startOffset int) (out string, i int) { + segment = strings.TrimSpace(segment) + segment += " " // Avoid overflow bugs with slicing + for i = startOffset; i < len(segment); i++ { + if !_is_op_byte(segment[i]) && i != startOffset { + return strings.TrimSpace(segment[startOffset:i]), (i - 1) + } + } + return strings.TrimSpace(segment[startOffset:]), (i - 1) +} + +func _skip_function_call(data string, index int) int { + var brace_count int + for ;index < len(data); index++{ + char := data[index] + if char == '(' { + brace_count++ + } else if char == ')' { + brace_count-- + if brace_count == 0 { + return index + } + } + } + return index +} + +func write_file(name string, content string) (err error) { + f, err := os.Create(name) + if err != nil { + return err + } + _, err = f.WriteString(content) + if err != nil { + return err + } + f.Sync() + f.Close() + return +} diff --git a/routes.go b/routes.go index 4d7caef0..eccb5b8d 100644 --- a/routes.go +++ b/routes.go @@ -3,7 +3,7 @@ package main import ( "log" - "fmt" + //"fmt" "strconv" "bytes" "regexp" @@ -142,7 +142,7 @@ func route_topics(w http.ResponseWriter, r *http.Request){ /*topicItem.CreatedAt, err = relative_time(topicItem.CreatedAt) if err != nil { - InternalError(err,w,r) + replyItem.CreatedAt = "" }*/ topicItem.LastReplyAt, err = relative_time(topicItem.LastReplyAt) if err != nil { @@ -347,6 +347,11 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){ } }*/ + topic.CreatedAt, err = relative_time(topic.CreatedAt) + if err != nil { + topic.CreatedAt = "" + } + // Calculate the offset last_page := int(topic.PostCount / items_per_page) + 1 if page > 1 { @@ -407,6 +412,11 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){ } }*/ + replyItem.CreatedAt, err = relative_time(replyItem.CreatedAt) + if err != nil { + replyItem.CreatedAt = "" + } + // We really shouldn't have inline HTML, we should do something about this... if replyItem.ActionType != "" { switch(replyItem.ActionType) { @@ -671,9 +681,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) { return } - var topic_name string - var fid, createdBy int - err = db.QueryRow("select title, parentID, createdBy from topics where tid = ?",tid).Scan(&topic_name,&fid,&createdBy) + topic, err := topics.CascadeGet(tid) if err == sql.ErrNoRows { PreError("Couldn't find the parent topic",w,r) return @@ -682,7 +690,7 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) { return } - user, ok := SimpleForumSessionCheck(w,r,fid) + user, ok := SimpleForumSessionCheck(w,r,topic.ParentID) if !ok { return } @@ -710,13 +718,13 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) { InternalError(err,w,r) return } - _, err = update_forum_cache_stmt.Exec(topic_name,tid,user.Name,user.ID,1) + _, err = update_forum_cache_stmt.Exec(topic.Title,tid,user.Name,user.ID,1) if err != nil { InternalError(err,w,r) return } - res, err := add_activity_stmt.Exec(user.ID,createdBy,"reply","topic",tid) + res, err := add_activity_stmt.Exec(user.ID,topic.CreatedBy,"reply","topic",tid) if err != nil { InternalError(err,w,r) return @@ -733,9 +741,14 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) { return } + // Alert the subscribers about this post without blocking this post from being posted + if enable_websockets { + go notify_watchers(lastId) + } + // Reload the topic... err = topics.Load(tid) - if err != nil && err != sql.ErrNoRows { + if err != nil && err == sql.ErrNoRows { LocalError("The destination no longer exists",w,r,user) return } else if err != nil { @@ -764,8 +777,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) { return } - var words, fid, createdBy int - err = db.QueryRow("select parentID, words, createdBy from topics where tid = ?", tid).Scan(&fid,&words,&createdBy) + topic, err := topics.CascadeGet(tid) if err == sql.ErrNoRows { PreError("The requested topic doesn't exist.",w,r) return @@ -774,7 +786,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) { return } - user, ok := SimpleForumSessionCheck(w,r,fid) + user, ok := SimpleForumSessionCheck(w,r,topic.ParentID) if !ok { return } @@ -783,12 +795,12 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) { return } - if createdBy == user.ID { + if topic.CreatedBy == user.ID { LocalError("You can't like your own topics",w,r,user) return } - err = has_liked_topic_stmt.QueryRow(user.ID, tid).Scan(&tid) + err = has_liked_topic_stmt.QueryRow(user.ID,tid).Scan(&tid) if err != nil && err != sql.ErrNoRows { InternalError(err,w,r) return @@ -797,7 +809,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) { return } - _, err = users.CascadeGet(createdBy) + _, err = users.CascadeGet(topic.CreatedBy) if err != nil && err == sql.ErrNoRows { LocalError("The target user doesn't exist",w,r,user) return @@ -819,7 +831,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) { return } - res, err := add_activity_stmt.Exec(user.ID,createdBy,"like","topic",tid) + res, err := add_activity_stmt.Exec(user.ID,topic.CreatedBy,"like","topic",tid) if err != nil { InternalError(err,w,r) return @@ -830,18 +842,18 @@ func route_like_topic(w http.ResponseWriter, r *http.Request) { return } - _, err = notify_one_stmt.Exec(createdBy,lastId) + _, err = notify_one_stmt.Exec(topic.CreatedBy,lastId) if err != nil { InternalError(err,w,r) return } // Live alerts, if the poster is online and WebSockets is enabled - _ = ws_hub.push_alert(createdBy,"like","topic",user.ID,createdBy,tid) + _ = ws_hub.push_alert(topic.CreatedBy,"like","topic",user.ID,topic.CreatedBy,tid) // Reload the topic... err = topics.Load(tid) - if err != nil && err != sql.ErrNoRows { + if err != nil && err == sql.ErrNoRows { LocalError("The liked topic no longer exists",w,r,user) return } else if err != nil { @@ -948,11 +960,7 @@ func route_reply_like_submit(w http.ResponseWriter, r *http.Request) { } // Live alerts, if the poster is online and WebSockets is enabled - fmt.Println("Calling push_alert") - err = ws_hub.push_alert(createdBy,"like","post",user.ID,createdBy,rid) - if err != nil { - fmt.Println(err.Error()) - } + _ = ws_hub.push_alert(createdBy,"like","post",user.ID,createdBy,rid) http.Redirect(w,r,"/topic/" + strconv.Itoa(tid),http.StatusSeeOther) } @@ -1031,7 +1039,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, sitem_id string var fid int = 1 var tid int - var title, content, data string + var title, content string if item_type == "reply" { err = db.QueryRow("select tid, content from replies where rid = ?", item_id).Scan(&tid, &content) if err == sql.ErrNoRows { @@ -1042,20 +1050,21 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, sitem_id string return } - err = db.QueryRow("select title, data from topics where tid = ?",tid).Scan(&title,&data) + topic, err := topics.CascadeGet(tid) if err == sql.ErrNoRows { - LocalError("We were unable to find the topic which the reported post is supposed to be in",w,r,user) + LocalError("We weren't able to find the topic the reported post is supposed to be in",w,r,user) return } else if err != nil { InternalError(err,w,r) return } - title = "Reply: " + title + + title = "Reply: " + topic.Title content = content + "\n\nOriginal Post: #rid-" + strconv.Itoa(item_id) } else if item_type == "user-reply" { err = db.QueryRow("select uid, content from users_replies where rid = ?", item_id).Scan(&tid, &content) if err == sql.ErrNoRows { - LocalError("We were unable to find the reported post",w,r,user) + LocalError("We weren't able to find the reported post",w,r,user) return } else if err != nil { InternalError(err,w,r) @@ -1064,7 +1073,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request, sitem_id string err = get_user_name_stmt.QueryRow(tid).Scan(&title) if err == sql.ErrNoRows { - LocalError("We were unable to find the profile which the reported post is supposed to be on",w,r,user) + LocalError("We weren't able to find the profile the reported post is supposed to be on",w,r,user) return } else if err != nil { InternalError(err,w,r) @@ -1315,8 +1324,8 @@ func route_account_own_edit_avatar_submit(w http.ResponseWriter, r *http.Request LocalError("This user no longer exists!",w,r,user) return } - noticeList = append(noticeList, "Your avatar was successfully updated") + noticeList = append(noticeList, "Your avatar was successfully updated") pi := Page{"Edit Avatar",user,noticeList,tList,nil} templates.ExecuteTemplate(w,"account-own-edit-avatar.html", pi) } @@ -1558,7 +1567,7 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) { return } - // Emergency password reset mechanism.. + // Admin password reset mechanism... if salt == "" { if password != real_password { LocalError("That's not the correct password.",w,r,user) @@ -1566,17 +1575,12 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) { } // Re-encrypt the password - SetPassword(uid, password) - } else { // Normal login.. - password = password + salt - if err != nil { - InternalError(err,w,r) - return - } + SetPassword(uid, real_password) - err := bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password)) - if err == bcrypt.ErrMismatchedHashAndPassword { - LocalError("That's not the correct password.",w,r,user) + // Fe-fetch the user data... + err = login_stmt.QueryRow(username).Scan(&uid, &username, &real_password, &salt) + if err == sql.ErrNoRows { + LocalError("That username doesn't exist anymore.",w,r,user) return } else if err != nil { InternalError(err,w,r) @@ -1584,6 +1588,21 @@ func route_login_submit(w http.ResponseWriter, r *http.Request) { } } + password = password + salt + if err != nil { + InternalError(err,w,r) + return + } + + err = bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password)) + if err == bcrypt.ErrMismatchedHashAndPassword { + LocalError("That's not the correct password.",w,r,user) + return + } else if err != nil { + InternalError(err,w,r) + return + } + session, err = GenerateSafeString(sessionLength) if err != nil { InternalError(err,w,r) diff --git a/template_list.go b/template_list.go index 177daeb2..d38a665a 100644 --- a/template_list.go +++ b/template_list.go @@ -237,7 +237,7 @@ var topic_alt_10 []byte = []byte(` topic_sticky_head`) var topic_alt_11 []byte = []byte(` topic_closed_head`) var topic_alt_12 []byte = []byte(`"> `) -var topic_alt_13 []byte = []byte(` +var topic_alt_13 []byte = []byte(` `) var topic_alt_14 []byte = []byte(`🔒︎`) var topic_alt_15 []byte = []byte(` @@ -290,81 +290,89 @@ var topic_alt_37 []byte = []byte(`' class="action_button">Unpin`) var topic_alt_38 []byte = []byte(`Pin`) var topic_alt_40 []byte = []byte(` - Report - `) + `) var topic_alt_43 []byte = []byte(``) var topic_alt_44 []byte = []byte(``) -var topic_alt_45 []byte = []byte(``) -var topic_alt_46 []byte = []byte(` up`) -var topic_alt_47 []byte = []byte(` +var topic_alt_45 []byte = []byte(` + `) +var topic_alt_46 []byte = []byte(` + `) +var topic_alt_47 []byte = []byte(``) +var topic_alt_48 []byte = []byte(` up`) +var topic_alt_49 []byte = []byte(`
`) -var topic_alt_48 []byte = []byte(` +var topic_alt_50 []byte = []byte(`
+var topic_alt_51 []byte = []byte(`action_item`) +var topic_alt_52 []byte = []byte(`">
 
+var topic_alt_53 []byte = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> 
`) -var topic_alt_53 []byte = []byte(` +var topic_alt_54 []byte = []byte(`" class="the_name">`) +var topic_alt_55 []byte = []byte(` `) -var topic_alt_54 []byte = []byte(`
`) -var topic_alt_56 []byte = []byte(`
+var topic_alt_61 []byte = []byte(`style="margin-left: 0px;"`) +var topic_alt_62 []byte = []byte(`> `) -var topic_alt_61 []byte = []byte(` +var topic_alt_63 []byte = []byte(` `) -var topic_alt_62 []byte = []byte(` +var topic_alt_64 []byte = []byte(` `) -var topic_alt_63 []byte = []byte(` +var topic_alt_65 []byte = []byte(` `) -var topic_alt_64 []byte = []byte(` +var topic_alt_66 []byte = []byte(`
`) -var topic_alt_65 []byte = []byte(`
+var topic_alt_67 []byte = []byte(`
`) -var topic_alt_66 []byte = []byte(`+1`) -var topic_alt_68 []byte = []byte(`Edit`) -var topic_alt_70 []byte = []byte(`Delete`) -var topic_alt_72 []byte = []byte(` - Report - `) -var topic_alt_75 []byte = []byte(``) -var topic_alt_76 []byte = []byte(``) -var topic_alt_77 []byte = []byte(``) -var topic_alt_78 []byte = []byte(` up`) +var topic_alt_68 []byte = []byte(`+1`) +var topic_alt_70 []byte = []byte(`Edit`) +var topic_alt_72 []byte = []byte(`Delete`) +var topic_alt_74 []byte = []byte(` + Report + `) +var topic_alt_77 []byte = []byte(``) +var topic_alt_78 []byte = []byte(``) var topic_alt_79 []byte = []byte(` + `) +var topic_alt_80 []byte = []byte(` + `) +var topic_alt_81 []byte = []byte(``) +var topic_alt_82 []byte = []byte(` up`) +var topic_alt_83 []byte = []byte(`
`) -var topic_alt_80 []byte = []byte(` +var topic_alt_84 []byte = []byte(`
`) -var topic_alt_81 []byte = []byte(` +var topic_alt_85 []byte = []byte(` `) -var topic_alt_82 []byte = []byte(` +var topic_alt_86 []byte = []byte(`
+var topic_alt_87 []byte = []byte(`' type="hidden" />
diff --git a/template_topic_alt.go b/template_topic_alt.go index 6b980247..e9359bab 100644 --- a/template_topic_alt.go +++ b/template_topic_alt.go @@ -1,8 +1,8 @@ // Code generated by. DO NOT EDIT. /* 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_topic_alt_handle = template_topic_alt @@ -105,6 +105,7 @@ w.Write([]byte(tmpl_topic_alt_vars.Topic.Content)) w.Write(topic_alt_28) w.Write([]byte(tmpl_topic_alt_vars.Topic.Content)) w.Write(topic_alt_29) +if tmpl_topic_alt_vars.CurrentUser.Loggedin { if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem { w.Write(topic_alt_30) w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) @@ -141,89 +142,98 @@ w.Write(topic_alt_43) w.Write([]byte(tmpl_topic_alt_vars.Topic.IpAddress)) w.Write(topic_alt_44) } -if tmpl_topic_alt_vars.Topic.LikeCount > 0 { -w.Write(topic_alt_45) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount))) -w.Write(topic_alt_46) } +w.Write(topic_alt_45) +w.Write([]byte(tmpl_topic_alt_vars.Topic.CreatedAt)) +w.Write(topic_alt_46) +if tmpl_topic_alt_vars.Topic.LikeCount > 0 { w.Write(topic_alt_47) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount))) +w.Write(topic_alt_48) +} +w.Write(topic_alt_49) if len(tmpl_topic_alt_vars.ItemList) != 0 { for _, item := range tmpl_topic_alt_vars.ItemList { -w.Write(topic_alt_48) -if item.ActionType != "" { -w.Write(topic_alt_49) -} w.Write(topic_alt_50) -w.Write([]byte(item.Avatar)) -w.Write(topic_alt_51) -w.Write([]byte(strconv.Itoa(item.CreatedBy))) -w.Write(topic_alt_52) -w.Write([]byte(item.CreatedByName)) -w.Write(topic_alt_53) -if item.Tag != "" { -w.Write(topic_alt_54) -w.Write([]byte(item.Tag)) -w.Write(topic_alt_55) -} else { -w.Write(topic_alt_56) -w.Write([]byte(strconv.Itoa(item.Level))) -w.Write(topic_alt_57) -} -w.Write(topic_alt_58) if item.ActionType != "" { +w.Write(topic_alt_51) +} +w.Write(topic_alt_52) +w.Write([]byte(item.Avatar)) +w.Write(topic_alt_53) +w.Write([]byte(strconv.Itoa(item.CreatedBy))) +w.Write(topic_alt_54) +w.Write([]byte(item.CreatedByName)) +w.Write(topic_alt_55) +if item.Tag != "" { +w.Write(topic_alt_56) +w.Write([]byte(item.Tag)) +w.Write(topic_alt_57) +} else { +w.Write(topic_alt_58) +w.Write([]byte(strconv.Itoa(item.Level))) w.Write(topic_alt_59) } w.Write(topic_alt_60) if item.ActionType != "" { w.Write(topic_alt_61) -w.Write([]byte(item.ActionIcon)) -w.Write(topic_alt_62) -w.Write([]byte(item.ActionType)) -w.Write(topic_alt_63) -} else { -w.Write(topic_alt_64) -w.Write([]byte(item.ContentHtml)) -w.Write(topic_alt_65) -if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem { -w.Write(topic_alt_66) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(topic_alt_67) } -if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply { +w.Write(topic_alt_62) +if item.ActionType != "" { +w.Write(topic_alt_63) +w.Write([]byte(item.ActionIcon)) +w.Write(topic_alt_64) +w.Write([]byte(item.ActionType)) +w.Write(topic_alt_65) +} else { +w.Write(topic_alt_66) +w.Write([]byte(item.ContentHtml)) +w.Write(topic_alt_67) +if tmpl_topic_alt_vars.CurrentUser.Loggedin { +if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem { w.Write(topic_alt_68) w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_69) } -if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply { +if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply { w.Write(topic_alt_70) w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_71) } +if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply { w.Write(topic_alt_72) w.Write([]byte(strconv.Itoa(item.ID))) w.Write(topic_alt_73) -w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) -w.Write(topic_alt_74) -if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { -w.Write(topic_alt_75) -w.Write([]byte(item.IpAddress)) -w.Write(topic_alt_76) } -if item.LikeCount > 0 { +w.Write(topic_alt_74) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(topic_alt_75) +w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) +w.Write(topic_alt_76) +if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { w.Write(topic_alt_77) -w.Write([]byte(strconv.Itoa(item.LikeCount))) +w.Write([]byte(item.IpAddress)) w.Write(topic_alt_78) } +} w.Write(topic_alt_79) -} +w.Write([]byte(item.CreatedAt)) w.Write(topic_alt_80) -} -} +if item.LikeCount > 0 { w.Write(topic_alt_81) -if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { +w.Write([]byte(strconv.Itoa(item.LikeCount))) w.Write(topic_alt_82) -w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +} w.Write(topic_alt_83) } +w.Write(topic_alt_84) +} +} +w.Write(topic_alt_85) +if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { +w.Write(topic_alt_86) +w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) +w.Write(topic_alt_87) +} w.Write(footer_0) } diff --git a/templates/topic_alt.html b/templates/topic_alt.html index 0e38688c..e2d9b13b 100644 --- a/templates/topic_alt.html +++ b/templates/topic_alt.html @@ -6,7 +6,7 @@
- {{.Topic.Title}} + {{.Topic.Title}} {{if .Topic.Is_Closed}}🔒︎{{end}} {{if .CurrentUser.Perms.EditTopic}} @@ -32,14 +32,16 @@
{{.Topic.Content}}
- {{if .CurrentUser.Perms.LikeItem}}+1{{end}} - {{if .CurrentUser.Perms.EditTopic}}Edit{{end}} - {{if .CurrentUser.Perms.DeleteTopic}}Delete{{end}} - {{if .CurrentUser.Perms.PinTopic}} - {{if .Topic.Sticky}}Unpin{{else}}Pin{{end}}{{end}} - Report - {{/* Element Queries might help with having to use JS. Who knows when or *if* the W3C will standardise it */}} - {{if .CurrentUser.Perms.ViewIPs}}{{.Topic.IpAddress}}{{end}} + {{if .CurrentUser.Loggedin}} + {{if .CurrentUser.Perms.LikeItem}}+1{{end}} + {{if .CurrentUser.Perms.EditTopic}}Edit{{end}} + {{if .CurrentUser.Perms.DeleteTopic}}Delete{{end}} + {{if .CurrentUser.Perms.PinTopic}} + {{if .Topic.Sticky}}Unpin{{else}}Pin{{end}}{{end}} + Report + {{if .CurrentUser.Perms.ViewIPs}}{{.Topic.IpAddress}}{{end}} + {{end}} + {{.Topic.CreatedAt}} {{if .Topic.LikeCount}}{{.Topic.LikeCount}} up{{end}}
@@ -58,11 +60,14 @@ {{else}}
{{.ContentHtml}}
- {{if $.CurrentUser.Perms.LikeItem}}+1{{end}} - {{if $.CurrentUser.Perms.EditReply}}Edit{{end}} - {{if $.CurrentUser.Perms.DeleteReply}}Delete{{end}} - Report - {{if $.CurrentUser.Perms.ViewIPs}}{{.IpAddress}}{{end}} + {{if $.CurrentUser.Loggedin}} + {{if $.CurrentUser.Perms.LikeItem}}+1{{end}} + {{if $.CurrentUser.Perms.EditReply}}Edit{{end}} + {{if $.CurrentUser.Perms.DeleteReply}}Delete{{end}} + Report + {{if $.CurrentUser.Perms.ViewIPs}}{{.IpAddress}}{{end}} + {{end}} + {{.CreatedAt}} {{if .LikeCount}}{{.LikeCount}} up{{end}}
{{end}} @@ -83,4 +88,4 @@
{{end}} -{{template "footer.html" . }} \ No newline at end of file +{{template "footer.html" . }} diff --git a/themes/tempra-conflux/public/main.css b/themes/tempra-conflux/public/main.css index 67fc2cea..925086b4 100644 --- a/themes/tempra-conflux/public/main.css +++ b/themes/tempra-conflux/public/main.css @@ -446,7 +446,6 @@ button.username { padding-right: 4px; padding-bottom: 2px; } -.post_item:last-child { padding-bottom: 7px; } .post_tag { margin-top: 0px; text-align: center; diff --git a/topic.go b/topic.go index c9928928..dbdb8ea6 100644 --- a/topic.go +++ b/topic.go @@ -22,6 +22,7 @@ type Topic struct PostCount int LikeCount int ClassName string // CSS Class Name + Data string // Used for report metadata } type TopicUser struct @@ -41,6 +42,7 @@ type TopicUser struct PostCount int LikeCount int ClassName string + Data string // Used for report metadata CreatedByName string Group int @@ -140,7 +142,7 @@ func (sts *StaticTopicStore) 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) + 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, &topic.Data) if err == nil { sts.Add(topic) } @@ -149,13 +151,13 @@ func (sts *StaticTopicStore) CascadeGet(id int) (*Topic, error) { func (sts *StaticTopicStore) BypassGet(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) + 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, &topic.Data) return topic, err } func (sts *StaticTopicStore) Load(id int) 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) + 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, &topic.Data) if err == nil { sts.Set(topic) } else { @@ -245,31 +247,31 @@ func NewSqlTopicStore() *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) + 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, &topic.Data) 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) + 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, &topic.Data) 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) + 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, &topic.Data) return &topic, err } func (sts *SqlTopicStore) BypassGet(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) + 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, &topic.Data) return topic, err } func (sus *SqlTopicStore) Load(id int) 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) + 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, &topic.Data) return err } @@ -336,7 +338,6 @@ func get_topicuser(tid int) (TopicUser,error) { //fmt.Printf("%+v\n", the_topic) tu.Tag = groups[tu.Group].Tag topics.Add(&the_topic) - //err = errors.Error("Loaded data via query") return tu, err } @@ -360,12 +361,13 @@ func copy_topic_to_topicuser(topic *Topic, user *User) (tu TopicUser) { tu.IpAddress = topic.IpAddress tu.PostCount = topic.PostCount tu.LikeCount = topic.LikeCount + tu.Data = topic.Data return tu } func get_topic_by_reply(rid int) (*Topic, error) { topic := Topic{ID:0} - err := get_topic_by_reply_stmt.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount) + err := get_topic_by_reply_stmt.QueryRow(rid).Scan(&topic.ID, &topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.Data) return &topic, err } diff --git a/user.go b/user.go index 610d52c3..725ae873 100644 --- a/user.go +++ b/user.go @@ -195,17 +195,6 @@ func (sts *StaticUserStore) AddUnsafe(item *User) error { return nil } -/*func (sts *StaticUserStore) SetConn(id int, conn interface{}) *User, error { - sts.Lock() - user, err := sts.CascadeGet(id) - sts.Unlock() - if err != nil { - return nil, err - } - user.WS_Conn = conn - return user, nil -}*/ - func (sts *StaticUserStore) Remove(id int) error { sts.Lock() delete(sts.items,id) @@ -336,8 +325,7 @@ func (sus *SqlUserStore) GetCapacity() int { } func (sus *SqlUserStore) GetLength() int { - // Return the total number of users registered on the forums - return 0 + return 0 // Return the total number of users registered on the forums? } func SetPassword(uid int, password string) (error) { @@ -364,10 +352,8 @@ func SendValidationEmail(username string, email string, token string) bool { if enable_ssl { schema = "s" } - subject := "Validate Your Email @ " + site_name msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. http" + schema + "://" + site_url + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused." - return SendEmail(email, subject, msg) } @@ -542,13 +528,13 @@ func decrease_post_user_stats(wcount int, uid int, topic bool, user User) error base_score = -2 } - if wcount >= settings["megapost_min_chars"].(int) { + if wcount >= settings["megapost_min_words"].(int) { _, err := increment_user_megaposts_stmt.Exec(-1,-1,-1,uid) if err != nil { return err } mod = 4 - } else if wcount >= settings["bigpost_min_chars"].(int) { + } else if wcount >= settings["bigpost_min_words"].(int) { _, err := increment_user_bigposts_stmt.Exec(-1,-1,uid) if err != nil { return err diff --git a/websockets.go b/websockets.go index c4464aaa..f4194c74 100644 --- a/websockets.go +++ b/websockets.go @@ -108,12 +108,55 @@ func(hub *WS_Hub) push_alert(targetUser int, event string, elementType string, a return err } - //fmt.Println("Writing to the client") w.Write([]byte(alert)) w.Close() return nil } +func(hub *WS_Hub) push_alerts(users []int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error { + //fmt.Println("In push_alerts") + var ws_users []*WS_User + hub.users.RLock() + // We don't want to keep a lock on this for too long, so we'll accept some nil pointers + for _, uid := range users { + ws_users = append(ws_users, hub.online_users[uid]) + } + hub.users.RUnlock() + if len(ws_users) == 0 { + return ws_nouser + } + + var errs []error + for _, ws_user := range ws_users { + if ws_user == nil { + continue + } + + //fmt.Println("Building alert") + alert, err := build_alert(event, elementType, actor_id, targetUser_id, elementID, *ws_user.User) + if err != nil { + errs = append(errs,err) + } + + //fmt.Println("Getting WS Writer") + w, err := ws_user.conn.NextWriter(websocket.TextMessage) + if err != nil { + errs = append(errs,err) + } + + w.Write([]byte(alert)) + w.Close() + } + + // Return the first error + if len(errs) != 0 { + for _, err := range errs { + return err + } + } + return nil +} + func route_websockets(w http.ResponseWriter, r *http.Request) { user, ok := SimpleSessionCheck(w,r) if !ok {