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(`"> Pin`) var topic_alt_40 []byte = []byte(` - Report - `) + `) var topic_alt_43 []byte = []byte(` `) -var topic_alt_45 []byte = []byte(` `) -var topic_alt_47 []byte = []byte(` +var topic_alt_45 []byte = []byte(` + + `) +var topic_alt_47 []byte = []byte(` `) +var topic_alt_49 []byte = []byte(`
`) -var topic_alt_48 []byte = []byte(` +var topic_alt_50 []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(`