From 1cc1e475822dca9fe7eadbeb108238bfaa283830 Mon Sep 17 00:00:00 2001 From: Azareal Date: Mon, 19 Jun 2017 09:06:54 +0100 Subject: [PATCH] Added an algorithm for sniffing out weak passwords upon registration. Themes can now inject their own stylesheets and JavaScript files into the header. Added friendlier errors for developers / designers when something goes wrong in the system. Moved three queries to the query generator. _process_where() in the query generator now uses an arbitrary expression parser, thus allowing more complex constructs like IN. Began work on the widget system. Added SimpleLeftJoin and SimpleInnerJoin to the inline query builder. Added SimpleInsertSelect and SimpleInsertInnerJoin to the query generator. _process_columns() now supports parametisation. Fixed a bug in _process_set() where _ was considered an invalid character in column names. Fixed a typo in the interpreter template mapping for topics. --- extend.go | 12 +- forum_store.go | 4 + gen_mysql.go | 111 ++++---- main.go | 26 +- mysql.go | 20 -- mysql.sql | 9 + pages.go | 7 + query_gen/lib/builder.go | 20 ++ query_gen/lib/mysql.go | 343 ++++++++++++++++++------- query_gen/lib/querygen.go | 55 +++- query_gen/lib/utils.go | 126 ++++++--- query_gen/main.go | 49 +++- routes.go | 35 ++- template_forum.go | 14 +- template_forums.go | 14 +- template_list.go | 21 +- template_profile.go | 16 +- template_topic.go | 14 +- template_topic_alt.go | 14 +- template_topics.go | 14 +- templates.go | 10 +- templates/footer.html | 6 +- templates/header.html | 6 +- templates/widget_simple.html | 6 + themes.go | 10 +- themes/tempra-simple/public/main.css | 17 +- themes/tempra-simple/public/sample.css | 1 + themes/tempra-simple/theme.json | 9 +- user.go | 65 ++++- utils.go | 52 ++++ widgets.go | 95 +++++++ 31 files changed, 952 insertions(+), 249 deletions(-) create mode 100644 templates/widget_simple.html create mode 100644 themes/tempra-simple/public/sample.css create mode 100644 widgets.go diff --git a/extend.go b/extend.go index 921f5dea..563e561c 100644 --- a/extend.go +++ b/extend.go @@ -49,7 +49,7 @@ type Plugin struct Init func() Activate func()error Deactivate func() - + Hooks map[string]int } @@ -65,7 +65,7 @@ func NewPlugin(uname string, name string, author string, url string, settings st Init: init, Activate: activate, Deactivate: deactivate, - + /* The Active field should never be altered by a plugin. It's used internally by the software to determine whether an admin has enabled a plugin or not and whether to run it. This will be overwritten by the user's preference. */ @@ -118,7 +118,11 @@ func init_plugins() { log.Print("Added plugin " + name) if body.Active { log.Print("Initialised plugin " + name) - plugins[name].Init() + if plugins[name].Init != nil { + plugins[name].Init() + } else { + log.Print("Plugin " + name + " doesn't have an initialiser.") + } } } plugins_inited = true @@ -137,4 +141,4 @@ func run_vhook(name string, data ...interface{}) interface{} { func run_vhook_noreturn(name string, data ...interface{}) { _ = vhooks[name](data...) -} \ No newline at end of file +} diff --git a/forum_store.go b/forum_store.go index f473373c..2fc50f05 100644 --- a/forum_store.go +++ b/forum_store.go @@ -6,6 +6,10 @@ import "errors" import "database/sql" import "./query_gen/lib" +var forums []Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency +var forum_perms map[int]map[int]ForumPerms // [gid][fid]Perms +var fstore ForumStore // :soon: +var forumCapCount int var err_noforum = errors.New("This forum doesn't exist") type ForumStore interface diff --git a/gen_mysql.go b/gen_mysql.go index 1959f65b..cad0d5e7 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -21,6 +21,7 @@ var get_forums_stmt *sql.Stmt var get_forums_permissions_stmt *sql.Stmt var get_plugins_stmt *sql.Stmt var get_themes_stmt *sql.Stmt +var get_widgets_stmt *sql.Stmt var is_plugin_active_stmt *sql.Stmt var get_users_stmt *sql.Stmt var is_theme_default_stmt *sql.Stmt @@ -109,6 +110,8 @@ var delete_topic_stmt *sql.Stmt var delete_profile_reply_stmt *sql.Stmt var delete_forum_perms_by_forum_stmt *sql.Stmt var report_exists_stmt *sql.Stmt +var add_forum_perms_to_forum_admins_stmt *sql.Stmt +var notify_watchers_stmt *sql.Stmt func gen_mysql() (err error) { if debug { @@ -205,6 +208,12 @@ func gen_mysql() (err error) { return err } + log.Print("Preparing get_widgets statement.") + get_widgets_stmt, err = db.Prepare("SELECT `position`,`side`,`type`,`active`,`location`,`data` FROM `widgets` ORDER BY position ASC") + if err != nil { + return err + } + log.Print("Preparing is_plugin_active statement.") is_plugin_active_stmt, err = db.Prepare("SELECT `active` FROM `plugins` WHERE `uname` = ?") if err != nil { @@ -248,13 +257,13 @@ func gen_mysql() (err error) { } log.Print("Preparing has_liked_topic statement.") - has_liked_topic_stmt, err = db.Prepare("SELECT `targetItem` FROM `likes` WHERE `sentBy` = ? AND `targetItem` = ? AND `targetType` = 'topics'") + has_liked_topic_stmt, err = db.Prepare("SELECT `targetItem` FROM `likes` WHERE `sentBy` = ? AND `targetItem` = ? AND `targetType` = 'topics'") if err != nil { return err } log.Print("Preparing has_liked_reply statement.") - has_liked_reply_stmt, err = db.Prepare("SELECT `targetItem` FROM `likes` WHERE `sentBy` = ? AND `targetItem` = ? AND `targetType` = 'replies'") + has_liked_reply_stmt, err = db.Prepare("SELECT `targetItem` FROM `likes` WHERE `sentBy` = ? AND `targetItem` = ? AND `targetType` = 'replies'") if err != nil { return err } @@ -302,25 +311,25 @@ func gen_mysql() (err error) { } 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 0,1") + forum_entry_exists_stmt, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' ORDER BY fid ASC LIMIT 0,1") if err != nil { return err } log.Print("Preparing group_entry_exists statement.") - group_entry_exists_stmt, err = db.Prepare("SELECT `gid` FROM `users_groups` WHERE `name` = '' ORDER BY gid ASC LIMIT 0,1") + group_entry_exists_stmt, err = db.Prepare("SELECT `gid` FROM `users_groups` WHERE `name` = '' ORDER BY gid ASC LIMIT 0,1") if err != nil { return err } log.Print("Preparing get_topic_replies_offset statement.") - get_topic_replies_offset_stmt, err = db.Prepare("SELECT `replies`.`rid`,`replies`.`content`,`replies`.`createdBy`,`replies`.`createdAt`,`replies`.`lastEdit`,`replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group`,`users`.`url_prefix`,`users`.`url_name`,`users`.`level`,`replies`.`ipaddress`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?") + get_topic_replies_offset_stmt, err = db.Prepare("SELECT `replies`.`rid`,`replies`.`content`,`replies`.`createdBy`,`replies`.`createdAt`,`replies`.`lastEdit`,`replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group`,`users`.`url_prefix`,`users`.`url_name`,`users`.`level`,`replies`.`ipaddress`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?") if err != nil { return err } log.Print("Preparing get_forum_topics_offset statement.") - get_forum_topics_offset_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`lastReplyAt`,`topics`.`parentID`,`topics`.`postCount`,`topics`.`likeCount`,`users`.`name`,`users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC LIMIT ?,?") + get_forum_topics_offset_stmt, err = db.Prepare("SELECT `topics`.`tid`,`topics`.`title`,`topics`.`content`,`topics`.`createdBy`,`topics`.`is_closed`,`topics`.`sticky`,`topics`.`createdAt`,`topics`.`lastReplyAt`,`topics`.`parentID`,`topics`.`postCount`,`topics`.`likeCount`,`users`.`name`,`users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC LIMIT ?,?") if err != nil { return err } @@ -350,7 +359,7 @@ func gen_mysql() (err error) { } 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 } @@ -482,223 +491,223 @@ func gen_mysql() (err error) { } log.Print("Preparing add_replies_to_topic statement.") - add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyAt` = NOW() WHERE `tid` = ? ") + add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyAt` = NOW() WHERE `tid` = ?") if err != nil { return err } log.Print("Preparing remove_replies_from_topic statement.") - remove_replies_from_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` - ? WHERE `tid` = ? ") + remove_replies_from_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` - ? WHERE `tid` = ?") if err != nil { return err } log.Print("Preparing add_topics_to_forum statement.") - add_topics_to_forum_stmt, err = db.Prepare("UPDATE `forums` SET `topicCount` = `topicCount` + ? WHERE `fid` = ? ") + add_topics_to_forum_stmt, err = db.Prepare("UPDATE `forums` SET `topicCount` = `topicCount` + ? WHERE `fid` = ?") if err != nil { return err } log.Print("Preparing remove_topics_from_forum statement.") - remove_topics_from_forum_stmt, err = db.Prepare("UPDATE `forums` SET `topicCount` = `topicCount` - ? WHERE `fid` = ? ") + remove_topics_from_forum_stmt, err = db.Prepare("UPDATE `forums` SET `topicCount` = `topicCount` - ? WHERE `fid` = ?") if err != nil { return err } log.Print("Preparing update_forum_cache statement.") - update_forum_cache_stmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = NOW() WHERE `fid` = ? ") + update_forum_cache_stmt, err = db.Prepare("UPDATE `forums` SET `lastTopic` = ?,`lastTopicID` = ?,`lastReplyer` = ?,`lastReplyerID` = ?,`lastTopicTime` = NOW() WHERE `fid` = ?") if err != nil { return err } log.Print("Preparing add_likes_to_topic statement.") - add_likes_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `likeCount` = `likeCount` + ? WHERE `tid` = ? ") + add_likes_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `likeCount` = `likeCount` + ? WHERE `tid` = ?") if err != nil { return err } log.Print("Preparing add_likes_to_reply statement.") - add_likes_to_reply_stmt, err = db.Prepare("UPDATE `replies` SET `likeCount` = `likeCount` + ? WHERE `rid` = ? ") + add_likes_to_reply_stmt, err = db.Prepare("UPDATE `replies` SET `likeCount` = `likeCount` + ? WHERE `rid` = ?") if err != nil { return err } log.Print("Preparing edit_topic statement.") - edit_topic_stmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ?,`is_closed` = ? WHERE `tid` = ? ") + edit_topic_stmt, err = db.Prepare("UPDATE `topics` SET `title` = ?,`content` = ?,`parsed_content` = ?,`is_closed` = ? WHERE `tid` = ?") if err != nil { return err } log.Print("Preparing edit_reply statement.") - edit_reply_stmt, err = db.Prepare("UPDATE `replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ? ") + edit_reply_stmt, err = db.Prepare("UPDATE `replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ?") if err != nil { return err } log.Print("Preparing stick_topic statement.") - stick_topic_stmt, err = db.Prepare("UPDATE `topics` SET `sticky` = 1 WHERE `tid` = ? ") + stick_topic_stmt, err = db.Prepare("UPDATE `topics` SET `sticky` = 1 WHERE `tid` = ?") if err != nil { return err } log.Print("Preparing unstick_topic statement.") - unstick_topic_stmt, err = db.Prepare("UPDATE `topics` SET `sticky` = 0 WHERE `tid` = ? ") + unstick_topic_stmt, err = db.Prepare("UPDATE `topics` SET `sticky` = 0 WHERE `tid` = ?") if err != nil { return err } log.Print("Preparing update_last_ip statement.") - update_last_ip_stmt, err = db.Prepare("UPDATE `users` SET `last_ip` = ? WHERE `uid` = ? ") + update_last_ip_stmt, err = db.Prepare("UPDATE `users` SET `last_ip` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing update_session statement.") - update_session_stmt, err = db.Prepare("UPDATE `users` SET `session` = ? WHERE `uid` = ? ") + update_session_stmt, err = db.Prepare("UPDATE `users` SET `session` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing logout statement.") - logout_stmt, err = db.Prepare("UPDATE `users` SET `session` = '' WHERE `uid` = ? ") + logout_stmt, err = db.Prepare("UPDATE `users` SET `session` = '' WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing set_password statement.") - set_password_stmt, err = db.Prepare("UPDATE `users` SET `password` = ?,`salt` = ? WHERE `uid` = ? ") + set_password_stmt, err = db.Prepare("UPDATE `users` SET `password` = ?,`salt` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing set_avatar statement.") - set_avatar_stmt, err = db.Prepare("UPDATE `users` SET `avatar` = ? WHERE `uid` = ? ") + set_avatar_stmt, err = db.Prepare("UPDATE `users` SET `avatar` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing set_username statement.") - set_username_stmt, err = db.Prepare("UPDATE `users` SET `name` = ? WHERE `uid` = ? ") + set_username_stmt, err = db.Prepare("UPDATE `users` SET `name` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing change_group statement.") - change_group_stmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ? ") + change_group_stmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing activate_user statement.") - activate_user_stmt, err = db.Prepare("UPDATE `users` SET `active` = 1 WHERE `uid` = ? ") + activate_user_stmt, err = db.Prepare("UPDATE `users` SET `active` = 1 WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing update_user_level statement.") - update_user_level_stmt, err = db.Prepare("UPDATE `users` SET `level` = ? WHERE `uid` = ? ") + update_user_level_stmt, err = db.Prepare("UPDATE `users` SET `level` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing increment_user_score statement.") - increment_user_score_stmt, err = db.Prepare("UPDATE `users` SET `score` = `score` + ? WHERE `uid` = ? ") + increment_user_score_stmt, err = db.Prepare("UPDATE `users` SET `score` = `score` + ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing increment_user_posts statement.") - increment_user_posts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ? WHERE `uid` = ? ") + increment_user_posts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing increment_user_bigposts statement.") - increment_user_bigposts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ?,`bigposts` = `bigposts` + ? WHERE `uid` = ? ") + increment_user_bigposts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ?,`bigposts` = `bigposts` + ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing increment_user_megaposts statement.") - increment_user_megaposts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ?,`bigposts` = `bigposts` + ?,`megaposts` = `megaposts` + ? WHERE `uid` = ? ") + increment_user_megaposts_stmt, err = db.Prepare("UPDATE `users` SET `posts` = `posts` + ?,`bigposts` = `bigposts` + ?,`megaposts` = `megaposts` + ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing increment_user_topics statement.") - increment_user_topics_stmt, err = db.Prepare("UPDATE `users` SET `topics` = `topics` + ? WHERE `uid` = ? ") + increment_user_topics_stmt, err = db.Prepare("UPDATE `users` SET `topics` = `topics` + ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing edit_profile_reply statement.") - edit_profile_reply_stmt, err = db.Prepare("UPDATE `users_replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ? ") + edit_profile_reply_stmt, err = db.Prepare("UPDATE `users_replies` SET `content` = ?,`parsed_content` = ? WHERE `rid` = ?") if err != nil { return err } log.Print("Preparing delete_forum statement.") - delete_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = '',`active` = 0 WHERE `fid` = ? ") + delete_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = '',`active` = 0 WHERE `fid` = ?") if err != nil { return err } log.Print("Preparing update_forum statement.") - update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ? ") + update_forum_stmt, err = db.Prepare("UPDATE `forums` SET `name` = ?,`desc` = ?,`active` = ?,`preset` = ? WHERE `fid` = ?") if err != nil { return err } log.Print("Preparing update_setting statement.") - update_setting_stmt, err = db.Prepare("UPDATE `settings` SET `content` = ? WHERE `name` = ? ") + update_setting_stmt, err = db.Prepare("UPDATE `settings` SET `content` = ? WHERE `name` = ?") if err != nil { return err } log.Print("Preparing update_plugin statement.") - update_plugin_stmt, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ? ") + update_plugin_stmt, err = db.Prepare("UPDATE `plugins` SET `active` = ? WHERE `uname` = ?") if err != nil { return err } log.Print("Preparing update_theme statement.") - update_theme_stmt, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ? ") + update_theme_stmt, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") if err != nil { return err } log.Print("Preparing update_user statement.") - update_user_stmt, err = db.Prepare("UPDATE `users` SET `name` = ?,`email` = ?,`group` = ? WHERE `uid` = ? ") + update_user_stmt, err = db.Prepare("UPDATE `users` SET `name` = ?,`email` = ?,`group` = ? WHERE `uid` = ?") if err != nil { return err } log.Print("Preparing update_group_perms statement.") - update_group_perms_stmt, err = db.Prepare("UPDATE `users_groups` SET `permissions` = ? WHERE `gid` = ? ") + update_group_perms_stmt, err = db.Prepare("UPDATE `users_groups` SET `permissions` = ? WHERE `gid` = ?") if err != nil { return err } log.Print("Preparing update_group_rank statement.") - update_group_rank_stmt, err = db.Prepare("UPDATE `users_groups` SET `is_admin` = ?,`is_mod` = ?,`is_banned` = ? WHERE `gid` = ? ") + update_group_rank_stmt, err = db.Prepare("UPDATE `users_groups` SET `is_admin` = ?,`is_mod` = ?,`is_banned` = ? WHERE `gid` = ?") if err != nil { return err } log.Print("Preparing update_group statement.") - update_group_stmt, err = db.Prepare("UPDATE `users_groups` SET `name` = ?,`tag` = ? WHERE `gid` = ? ") + update_group_stmt, err = db.Prepare("UPDATE `users_groups` SET `name` = ?,`tag` = ? WHERE `gid` = ?") 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` = ? ") + update_email_stmt, err = db.Prepare("UPDATE `emails` SET `email` = ?,`uid` = ?,`validated` = ?,`token` = ? WHERE `email` = ?") if err != nil { return err } log.Print("Preparing verify_email statement.") - verify_email_stmt, err = db.Prepare("UPDATE `emails` SET `validated` = 1,`token` = '1' WHERE `email` = ? ") + verify_email_stmt, err = db.Prepare("UPDATE `emails` SET `validated` = 1,`token` = '' WHERE `email` = ?") if err != nil { return err } @@ -728,7 +737,19 @@ func gen_mysql() (err error) { } log.Print("Preparing report_exists statement.") - report_exists_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `topics` WHERE `data` = ? AND `data` != '' AND `parentID` = 1") + report_exists_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `topics` WHERE `data` = ? AND `data` != '' AND `parentID` = 1") + 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 { + return err + } + + log.Print("Preparing notify_watchers statement.") + notify_watchers_stmt, err = db.Prepare("INSERT INTO `activity_stream_matches`(`watcher`,`asid`) SELECT `activity_subscriptions`.`user`, `activity_stream`.`asid` 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 } diff --git a/main.go b/main.go index dfeb099b..b12bc378 100644 --- a/main.go +++ b/main.go @@ -36,15 +36,12 @@ var enable_websockets bool = false // Don't change this, the value is overwritte var startTime time.Time var timeLocation *time.Location var templates = template.New("") -var no_css_tmpl = template.CSS("") -var staff_css_tmpl = template.CSS(staff_css) +var no_css_tmpl template.CSS = template.CSS("") +var staff_css_tmpl template.CSS = template.CSS(staff_css) var settings map[string]interface{} = make(map[string]interface{}) var external_sites map[string]string = make(map[string]string) var groups []Group -var forums []Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency -var forum_perms map[int]map[int]ForumPerms // [gid][fid]Perms -var fstore ForumStore // :soon: -var groupCapCount, forumCapCount int +var groupCapCount int var static_files map[string]SFile = make(map[string]SFile) var template_topic_handle func(TopicPage,io.Writer) = nil @@ -62,6 +59,9 @@ func compile_templates() { NoticeList:[]string{"test"}, Stylesheets:[]string{"panel"}, Scripts:[]string{"whatever"}, + Sidebars:HeaderSidebars{ + Left: template.HTML("lalala"), + }, } log.Print("Compiling the templates") @@ -87,18 +87,18 @@ func compile_templates() { } varList = make(map[string]VarItem) forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData} - forums_tmpl := c.compile_template("forums.html","templates/","ForumsPage", forums_page, varList) + forums_tmpl := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList) var topicsList []TopicsRow topicsList = append(topicsList,TopicsRow{1,"Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","Admin","","",0,"","","","",58,"General"}) topics_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData} - topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage", topics_page, varList) + topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList) var topicList []TopicUser 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,headerVars,topicList,forum_item,1,1,extData} - forum_tmpl := c.compile_template("forum.html","templates/","ForumPage", forum_page, varList) + forum_tmpl := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList) log.Print("Writing the templates") go write_template("topic", topic_id_tmpl) @@ -209,6 +209,13 @@ func main(){ hooks["rrow_assign"] = nil init_plugins() + log.Print("Initialising the widgets") + err = init_widgets() + if err != nil { + log.Fatal(err) + } + + log.Print("Initialising the router") router := NewGenRouter(http.FileServer(http.Dir("./uploads"))) ///router.HandleFunc("/static/", route_static) ///router.HandleFunc("/overview/", route_overview) @@ -300,6 +307,7 @@ func main(){ // pprof.StopCPUProfile() //} + log.Print("Initialising the HTTP server") if !enable_ssl { if server_port == "" { server_port = "80" diff --git a/mysql.go b/mysql.go index 892c0860..0e0a8d28 100644 --- a/mysql.go +++ b/mysql.go @@ -3,15 +3,12 @@ package main import "log" -import "strings" import "database/sql" import _ "github.com/go-sql-driver/mysql" import "./query_gen/lib" -var notify_watchers_stmt *sql.Stmt var get_activity_feed_by_watcher_stmt *sql.Stmt var get_activity_count_by_watcher_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 var update_forum_perms_for_group_stmt *sql.Stmt @@ -56,12 +53,6 @@ func _init_database() (err error) { return err } - log.Print("Preparing notify_watchers statement.") - notify_watchers_stmt, err = db.Prepare("INSERT INTO activity_stream_matches(watcher, asid) SELECT activity_subscriptions.user, activity_stream.asid 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 - } - 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 { @@ -74,12 +65,6 @@ func _init_database() (err error) { 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 { - return err - } - log.Print("Preparing add_forum_perms_to_forum_staff statement.") add_forum_perms_to_forum_staff_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 = 0 AND is_mod = 1") if err != nil { @@ -118,8 +103,3 @@ func _init_database() (err error) { return nil } - -// Temporary hack so that we can move all the raw queries out of the other files and into here -func topic_list_query(visible_fids []string) string { - return "select topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, topics.postCount, topics.likeCount, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid where parentID in("+strings.Join(visible_fids,",")+") order by topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC" -} diff --git a/mysql.sql b/mysql.sql index 718c8a41..a6453283 100644 --- a/mysql.sql +++ b/mysql.sql @@ -174,6 +174,15 @@ CREATE TABLE `themes`( unique(`uname`) ); +CREATE TABLE `widgets`( + `position` int not null, + `side` varchar(100) not null, + `type` varchar(100) not null, + `active` tinyint(1) DEFAULT 0 not null, + `location` varchar(100) not null, + `data` text DEFAULT '' not null +); + CREATE TABLE `moderation_logs`( `action` varchar(100) not null, `elementID` int not null, diff --git a/pages.go b/pages.go index 5df8bdaf..06faaf84 100644 --- a/pages.go +++ b/pages.go @@ -13,6 +13,13 @@ type HeaderVars struct NoticeList []string Scripts []string Stylesheets []string + Sidebars HeaderSidebars +} + +type HeaderSidebars struct +{ + Left template.HTML + Right template.HTML } type ExtData struct diff --git a/query_gen/lib/builder.go b/query_gen/lib/builder.go index 372e9860..a5f777ef 100644 --- a/query_gen/lib/builder.go +++ b/query_gen/lib/builder.go @@ -1,5 +1,7 @@ /* WIP Under Construction */ package qgen + +//import "fmt" import "database/sql" var Builder *builder @@ -36,6 +38,24 @@ func (build *builder) SimpleSelect(table string, columns string, where string, o return build.conn.Prepare(res) } +func (build *builder) SimpleLeftJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) + if err != nil { + return stmt, err + } + //fmt.Println("res",res) + return build.conn.Prepare(res) +} + +func (build *builder) SimpleInnerJoin(table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt, err error) { + res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit) + if err != nil { + return stmt, err + } + //fmt.Println("res",res) + return build.conn.Prepare(res) +} + func (build *builder) SimpleInsert(table string, columns string, fields string) (stmt *sql.Stmt, err error) { res, err := build.adapter.SimpleInsert("_builder", table, columns, fields) if err != nil { diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index 97be2b62..4dc8fc2e 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -135,24 +135,23 @@ func (adapter *Mysql_Adapter) SimpleUpdate(name string, table string, set string // Remove the trailing comma querystr = querystr[0:len(querystr) - 1] + // Add support for BETWEEN x.x if len(where) != 0 { querystr += " WHERE" for _, loc := range _process_where(where) { - var left, right string - - if loc.LeftType == "column" { - left = "`" + loc.LeftColumn + "`" - } else { - left = loc.LeftColumn + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + querystr += " `" + token.Contents + "`" + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } } - - if loc.RightType == "column" { - right = "`" + loc.RightColumn + "`" - } else { - right = loc.RightColumn - } - - querystr += " " + left + " " + loc.Operator + " " + right + " AND " + querystr += " AND" } querystr = querystr[0:len(querystr) - 4] } @@ -173,22 +172,22 @@ func (adapter *Mysql_Adapter) SimpleDelete(name string, table string, where stri } var querystr string = "DELETE FROM `" + table + "` WHERE" + + // Add support for BETWEEN x.x for _, loc := range _process_where(where) { - var left, right string - - if loc.LeftType == "column" { - left = "`" + loc.LeftColumn + "`" - } else { - left = loc.LeftColumn + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + querystr += " `" + token.Contents + "`" + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } } - - if loc.RightType == "column" { - right = "`" + loc.RightColumn + "`" - } else { - right = loc.RightColumn - } - - querystr += " " + left + " " + loc.Operator + " " + right + " AND " + querystr += " AND" } querystr = strings.TrimSpace(querystr[0:len(querystr) - 4]) @@ -232,24 +231,24 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st querystr = querystr[0:len(querystr) - 1] querystr += " FROM `" + table + "`" + + // Add support for BETWEEN x.x if len(where) != 0 { querystr += " WHERE" for _, loc := range _process_where(where) { - var left, right string - - if loc.LeftType == "column" { - left = "`" + loc.LeftColumn + "`" - } else { - left = loc.LeftColumn + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + querystr += " `" + token.Contents + "`" + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } } - - if loc.RightType == "column" { - right = "`" + loc.RightColumn + "`" - } else { - right = loc.RightColumn - } - - querystr += " " + left + " " + loc.Operator + " " + right + " AND " + querystr += " AND" } querystr = querystr[0:len(querystr) - 4] } @@ -318,28 +317,28 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2 // Remove the trailing AND querystr = querystr[0:len(querystr) - 4] + // Add support for BETWEEN x.x 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 + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + halves := strings.Split(token.Contents,".") + if len(halves) == 2 { + querystr += " `" + halves[0] + "`.`" + halves[1] + "`" + } else { + querystr += " `" + token.Contents + "`" + } + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } } - - 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 += " AND" } querystr = querystr[0:len(querystr) - 4] } @@ -408,28 +407,28 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2 // Remove the trailing AND querystr = querystr[0:len(querystr) - 4] + // Add support for BETWEEN x.x 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 + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + halves := strings.Split(token.Contents,".") + if len(halves) == 2 { + querystr += " `" + halves[0] + "`.`" + halves[1] + "`" + } else { + querystr += " `" + token.Contents + "`" + } + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } } - - 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 += " AND" } querystr = querystr[0:len(querystr) - 4] } @@ -451,6 +450,163 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2 return querystr, nil } +func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) { + /* Insert Portion */ + var querystr string = "INSERT INTO `" + ins.Table + "`(" + + // Escape the column names, just in case we've used a reserved keyword + for _, column := range _process_columns(ins.Columns) { + if column.Type == "function" { + querystr += column.Left + "," + } else { + querystr += "`" + column.Left + "`," + } + } + querystr = querystr[0:len(querystr) - 1] + ") SELECT" + + /* Select Portion */ + + for _, column := range _process_columns(sel.Columns) { + var source, alias string + + // Escape the column names, just in case we've used a reserved keyword + if column.Type == "function" || column.Type == "substitute" { + source = column.Left + } else { + source = "`" + column.Left + "`" + } + + if column.Alias != "" { + alias = " AS `" + column.Alias + "`" + } + querystr += " " + source + alias + "," + } + querystr = querystr[0:len(querystr) - 1] + + querystr += " FROM `" + sel.Table + "`" + + // Add support for BETWEEN x.x + if len(sel.Where) != 0 { + querystr += " WHERE" + for _, loc := range _process_where(sel.Where) { + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + querystr += " `" + token.Contents + "`" + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } + } + querystr += " AND" + } + querystr = querystr[0:len(querystr) - 4] + } + + if len(sel.Orderby) != 0 { + querystr += " ORDER BY " + for _, column := range _process_orderby(sel.Orderby) { + querystr += column.Column + " " + strings.ToUpper(column.Order) + "," + } + querystr = querystr[0:len(querystr) - 1] + } + + if sel.Limit != "" { + querystr += " LIMIT " + sel.Limit + } + + querystr = strings.TrimSpace(querystr) + adapter.push_statement(name,querystr) + return querystr, nil +} + +func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { + /* Insert Portion */ + var querystr string = "INSERT INTO `" + ins.Table + "`(" + + // Escape the column names, just in case we've used a reserved keyword + for _, column := range _process_columns(ins.Columns) { + if column.Type == "function" { + querystr += column.Left + "," + } else { + querystr += "`" + column.Left + "`," + } + } + querystr = querystr[0:len(querystr) - 1] + ") SELECT" + + /* Select Portion */ + + for _, column := range _process_columns(sel.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 + "," + } + querystr = querystr[0:len(querystr) - 1] + + querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + for _, joiner := range _process_joiner(sel.Joiners) { + querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " + } + querystr = querystr[0:len(querystr) - 4] + + // Add support for BETWEEN x.x + if len(sel.Where) != 0 { + querystr += " WHERE" + for _, loc := range _process_where(sel.Where) { + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + halves := strings.Split(token.Contents,".") + if len(halves) == 2 { + querystr += " `" + halves[0] + "`.`" + halves[1] + "`" + } else { + querystr += " `" + token.Contents + "`" + } + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } + } + querystr += " AND" + } + querystr = querystr[0:len(querystr) - 4] + } + + if len(sel.Orderby) != 0 { + querystr += " ORDER BY " + for _, column := range _process_orderby(sel.Orderby) { + querystr += column.Column + " " + strings.ToUpper(column.Order) + "," + } + querystr = querystr[0:len(querystr) - 1] + } + + if sel.Limit != "" { + querystr += " LIMIT " + sel.Limit + } + + querystr = strings.TrimSpace(querystr) + adapter.push_statement(name,querystr) + return querystr, nil +} + func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where string, limit string) (string, error) { if name == "" { return "", errors.New("You need a name for this statement") @@ -460,24 +616,27 @@ func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where strin } var querystr string = "SELECT COUNT(*) AS `count` FROM `" + table + "`" + + // Add support for BETWEEN x.x if len(where) != 0 { querystr += " WHERE" + //fmt.Println("SimpleCount:",name) + //fmt.Println("where:",where) + //fmt.Println("_process_where:",_process_where(where)) for _, loc := range _process_where(where) { - var left, right string - - if loc.LeftType == "column" { - left = "`" + loc.LeftColumn + "`" - } else { - left = loc.LeftColumn + for _, token := range loc.Expr { + switch(token.Type) { + case "function","operator","number","substitute": + querystr += " " + token.Contents + "" + case "column": + querystr += " `" + token.Contents + "`" + case "string": + querystr += " '" + token.Contents + "'" + default: + panic("This token doesn't exist o_o") + } } - - if loc.RightType == "column" { - right = "`" + loc.RightColumn + "`" - } else { - right = loc.RightColumn - } - - querystr += " " + left + " " + loc.Operator + " " + right + " AND " + querystr += " AND" } querystr = querystr[0:len(querystr) - 4] } diff --git a/query_gen/lib/querygen.go b/query_gen/lib/querygen.go index 251e54fb..bdb070ea 100644 --- a/query_gen/lib/querygen.go +++ b/query_gen/lib/querygen.go @@ -6,6 +6,51 @@ import "errors" var DB_Registry []DB_Adapter var No_Adapter = errors.New("This adapter doesn't exist") +type DB_Select struct +{ + Table string + Columns string + Where string + Orderby string + Limit string +} + +type DB_Join struct +{ + Table1 string + Table2 string + Columns string + Joiners string + Where string + Orderby string + Limit string +} + +type DB_Insert struct +{ + Table string + Columns string + Fields string +} + +/*type DB_Select struct +{ + Name string + Table string + Columns []DB_Column + Where []DB_Where + Orderby []DB_Order + Limit DB_Limit +} + +type DB_Insert struct +{ + Name string + Table string + Columns []DB_Column + Fields []DB_Field +}*/ + type DB_Column struct { Table string @@ -22,13 +67,7 @@ type DB_Field struct type DB_Where struct { - LeftTable string - LeftColumn string - RightTable string - RightColumn string - Operator string - LeftType string - RightType string + Expr []DB_Token // Simple expressions, the innards of functions are opaque for now. } type DB_Joiner struct @@ -71,6 +110,8 @@ type DB_Adapter interface { SimpleSelect(string,string,string,string,string,string) (string, error) SimpleLeftJoin(string,string,string,string,string,string,string,string) (string, error) SimpleInnerJoin(string,string,string,string,string,string,string,string) (string, error) + SimpleInsertSelect(string,DB_Insert,DB_Select) (string,error) + SimpleInsertInnerJoin(string,DB_Insert,DB_Join) (string,error) SimpleCount(string,string,string,string) (string, error) Write() error diff --git a/query_gen/lib/utils.go b/query_gen/lib/utils.go index b083219f..9d69cd2a 100644 --- a/query_gen/lib/utils.go +++ b/query_gen/lib/utils.go @@ -28,6 +28,8 @@ func _process_columns(colstr string) (columns []DB_Column) { } if halves[0][len(halves[0]) - 1] == ')' { outcol.Type = "function" + } else if halves[0] == "?" { + outcol.Type = "substitute" } else { outcol.Type = "column" } @@ -83,42 +85,97 @@ func _process_joiner(joinstr string) (joiner []DB_Joiner) { 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) + + var buffer string + var optype int // 0: None, 1: Number, 2: Column, 3: Function, 4: String, 5: Operator 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]) + var tmp_where DB_Where + segment += ")" + for i := 0; i < len(segment); i++ { + char := segment[i] + //fmt.Println("optype",optype) + switch(optype) { + case 0: // unknown + //fmt.Println("case 0:",char,string(char)) + if ('0' <= char && char <= '9') { + optype = 1 + buffer = string(char) + } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' { + optype = 2 + buffer = string(char) + } else if char == '\'' { + optype = 4 + buffer = "" + } else if _is_op_byte(char) { + optype = 5 + buffer = string(char) + } else if char == '?' { + //fmt.Println("Expr:","?") + tmp_where.Expr = append(tmp_where.Expr,DB_Token{"?","substitute"}) + } + case 1: // number + if ('0' <= char && char <= '9') { + buffer += string(char) + } else { + optype = 0 + i-- + //fmt.Println("Expr:",buffer) + tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"number"}) + } + case 2: // column + if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '.' || char == '_' { + buffer += string(char) + } else if char == '(' { + optype = 3 + i-- + } else { + optype = 0 + i-- + //fmt.Println("Expr:",buffer) + tmp_where.Expr = append(tmp_where.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(segment,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 += segment[pre_i:i] + string(segment[i]) + //fmt.Println("Expr:",buffer) + tmp_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"function"}) + optype = 0 + case 4: // string + if char != '\'' { + buffer += string(char) + } else { + optype = 0 + //fmt.Println("Expr:",buffer) + tmp_where.Expr = append(tmp_where.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_where.Expr = append(tmp_where.Expr,DB_Token{buffer,"operator"}) + } + default: + panic("Bad optype in _process_where") + } } - - 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) + where = append(where,tmp_where) } return where } @@ -170,11 +227,12 @@ func _process_set(setstr string) (setter []DB_Setter) { if ('0' <= char && char <= '9') { optype = 1 buffer = string(char) - } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') { + } else if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' { optype = 2 buffer = string(char) } else if char == '\'' { optype = 4 + buffer = "" } else if _is_op_byte(char) { optype = 5 buffer = string(char) @@ -192,7 +250,7 @@ func _process_set(setstr string) (setter []DB_Setter) { tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"number"}) } case 2: // column - if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') { + if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' { buffer += string(char) } else if char == '(' { optype = 3 @@ -235,6 +293,8 @@ func _process_set(setstr string) (setter []DB_Setter) { //fmt.Println("Expr:",buffer) tmp_setter.Expr = append(tmp_setter.Expr,DB_Token{buffer,"operator"}) } + default: + panic("Bad optype in _process_set") } } setter = append(setter,tmp_setter) @@ -258,6 +318,10 @@ func _is_op_byte(char byte) bool { return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' } +func _is_op_rune(char rune) bool { + return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/' +} + func _process_fields(fieldstr string) (fields []DB_Field) { if fieldstr == "" { return fields diff --git a/query_gen/main.go b/query_gen/main.go index d213d02e..5f6a22f3 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -18,34 +18,57 @@ func write_statements(adapter qgen.DB_Adapter) error { if err != nil { return err } + err = write_left_joins(adapter) if err != nil { return err } + err = write_inner_joins(adapter) if err != nil { return err } + err = write_inserts(adapter) if err != nil { return err } + err = write_replaces(adapter) if err != nil { return err } + err = write_updates(adapter) if err != nil { return err } + err = write_deletes(adapter) if err != nil { return err } + err = write_simple_counts(adapter) if err != nil { return err } + + err = write_insert_selects(adapter) + if err != nil { + return err + } + + err = write_insert_left_joins(adapter) + if err != nil { + return err + } + + err = write_insert_inner_joins(adapter) + if err != nil { + return err + } + return nil } @@ -84,6 +107,8 @@ func write_selects(adapter qgen.DB_Adapter) error { adapter.SimpleSelect("get_themes","themes","uname, default","","","") + adapter.SimpleSelect("get_widgets","widgets","position, side, type, active, location, data","","position ASC","") + adapter.SimpleSelect("is_plugin_active","plugins","active","uname = ?","","") adapter.SimpleSelect("get_users","users","uid, name, group, active, is_super_admin, avatar","","","") @@ -291,7 +316,29 @@ func write_deletes(adapter qgen.DB_Adapter) error { } func write_simple_counts(adapter qgen.DB_Adapter) error { - adapter.SimpleCount("report_exists","topics","data = ? and data != '' and parentID = 1","") + adapter.SimpleCount("report_exists","topics","data = ? AND data != '' AND parentID = 1","") + + return nil +} + +func write_insert_selects(adapter qgen.DB_Adapter) error { + adapter.SimpleInsertSelect("add_forum_perms_to_forum_admins", + qgen.DB_Insert{"forums_permissions","gid,fid,preset,permissions",""}, + qgen.DB_Select{"users_groups","gid, ? AS fid, ? AS preset, ? AS permissions","is_admin = 1","",""}, + ) + + return nil +} + +func write_insert_left_joins(adapter qgen.DB_Adapter) error { + return nil +} + +func write_insert_inner_joins(adapter qgen.DB_Adapter) error { + adapter.SimpleInsertInnerJoin("notify_watchers", + qgen.DB_Insert{"activity_stream_matches","watcher, asid",""}, + qgen.DB_Join{"activity_stream","activity_subscriptions","activity_subscriptions.user, activity_stream.asid","activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor","asid = ?","",""}, + ) return nil } diff --git a/routes.go b/routes.go index f70c26d9..1a1166ce 100644 --- a/routes.go +++ b/routes.go @@ -16,6 +16,8 @@ import ( "html" "html/template" "database/sql" + + "./query_gen/lib" ) import _ "github.com/go-sql-driver/mysql" @@ -104,16 +106,25 @@ func route_topics(w http.ResponseWriter, r *http.Request){ return } - var fidList []string + var qlist string + var fidList []interface{} group := groups[user.Group] for _, fid := range group.CanSee { if forums[fid].Name != "" { fidList = append(fidList,strconv.Itoa(fid)) + qlist += "?," } } + qlist = qlist[0:len(qlist) - 1] var topicList []TopicsRow - rows, err := db.Query(topic_list_query(fidList)) + stmt, err := qgen.Builder.SimpleLeftJoin("topics","users","topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, topics.postCount, topics.likeCount, users.name, users.avatar","topics.createdBy = users.uid","parentID IN("+qlist+")","topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC","") + if err != nil { + InternalError(err,w,r) + return + } + + rows, err := stmt.Query(fidList...) if err != nil { InternalError(err,w,r) return @@ -166,9 +177,9 @@ func route_topics(w http.ResponseWriter, r *http.Request){ if template_topics_handle != nil { template_topics_handle(pi,w) } else { - mapping, ok := themes[defaultTheme].TemplatesMap["topic"] + mapping, ok := themes[defaultTheme].TemplatesMap["topics"] if !ok { - mapping = "topic" + mapping = "topics" } err = templates.ExecuteTemplate(w,mapping + ".html", pi) if err != nil { @@ -1689,8 +1700,20 @@ func route_register_submit(w http.ResponseWriter, r *http.Request) { LocalError("You didn't put in a password.",w,r,user) return } - if password == "test" || password == "123456" || password == "123" || password == "password" { - LocalError("Your password is too weak.",w,r,user) + + if password == username { + LocalError("You can't use your username as your password.",w,r,user) + return + } + + if password == email { + LocalError("You can't use your email as your password.",w,r,user) + return + } + + err = weak_password(password) + if err != nil { + LocalError(err.Error(),w,r,user) return } diff --git a/template_forum.go b/template_forum.go index 2db4e46e..6db71038 100644 --- a/template_forum.go +++ b/template_forum.go @@ -51,11 +51,15 @@ w.Write(menu_6) } w.Write(menu_7) w.Write(header_9) +if tmpl_forum_vars.Header.Sidebars.Right != "" { +w.Write(header_10) +} +w.Write(header_11) if len(tmpl_forum_vars.Header.NoticeList) != 0 { for _, item := range tmpl_forum_vars.Header.NoticeList { -w.Write(header_10) +w.Write(header_12) w.Write([]byte(item)) -w.Write(header_11) +w.Write(header_13) } } if tmpl_forum_vars.Page > 1 { @@ -133,4 +137,10 @@ w.Write(forum_31) } w.Write(forum_32) w.Write(footer_0) +if tmpl_forum_vars.Header.Sidebars.Right != "" { +w.Write(footer_1) +w.Write([]byte(string(tmpl_forum_vars.Header.Sidebars.Right))) +w.Write(footer_2) +} +w.Write(footer_3) } diff --git a/template_forums.go b/template_forums.go index 45cb1fa7..736b7a75 100644 --- a/template_forums.go +++ b/template_forums.go @@ -51,11 +51,15 @@ w.Write(menu_6) } w.Write(menu_7) w.Write(header_9) +if tmpl_forums_vars.Header.Sidebars.Right != "" { +w.Write(header_10) +} +w.Write(header_11) if len(tmpl_forums_vars.Header.NoticeList) != 0 { for _, item := range tmpl_forums_vars.Header.NoticeList { -w.Write(header_10) +w.Write(header_12) w.Write([]byte(item)) -w.Write(header_11) +w.Write(header_13) } } w.Write(forums_0) @@ -106,4 +110,10 @@ w.Write(forums_20) } w.Write(forums_21) w.Write(footer_0) +if tmpl_forums_vars.Header.Sidebars.Right != "" { +w.Write(footer_1) +w.Write([]byte(string(tmpl_forums_vars.Header.Sidebars.Right))) +w.Write(footer_2) +} +w.Write(footer_3) } diff --git a/template_list.go b/template_list.go index d822ca10..85fe900e 100644 --- a/template_list.go +++ b/template_list.go @@ -9,14 +9,14 @@ var header_1 []byte = []byte(` `) var header_2 []byte = []byte(` +var header_3 []byte = []byte(`" rel="stylesheet" type="text/css"> `) var header_4 []byte = []byte(` `) var header_5 []byte = []byte(` +var header_6 []byte = []byte(`"> `) var header_7 []byte = []byte(` {{range .Header.Scripts}} - + {{end}} @@ -18,5 +18,5 @@
{{template "menu.html" .}} -
+
{{range .Header.NoticeList}}
{{.}}
{{end}} diff --git a/templates/widget_simple.html b/templates/widget_simple.html new file mode 100644 index 00000000..cd18d4a3 --- /dev/null +++ b/templates/widget_simple.html @@ -0,0 +1,6 @@ +
+
{{.Name}}
+
+
+
{{.Text}}
+
diff --git a/themes.go b/themes.go index 2b39b41c..96f7acb8 100644 --- a/themes.go +++ b/themes.go @@ -32,9 +32,11 @@ type Theme struct ForkOf string Tag string URL string + Sidebars string // Allowed Values: left, right, both, false Settings map[string]ThemeSetting Templates []TemplateMapping - TemplatesMap map[string]string // TO-DO: Make template mapping work without the template compiler + TemplatesMap map[string]string + Resources []ThemeResource // This variable should only be set and unset by the system, not the theme meta file Active bool @@ -53,6 +55,12 @@ type TemplateMapping struct //When string } +type ThemeResource struct +{ + Name string + Location string +} + func LoadThemes() error { rows, err := get_themes_stmt.Query() if err != nil { diff --git a/themes/tempra-simple/public/main.css b/themes/tempra-simple/public/main.css index 7a0bd8c6..703971a1 100644 --- a/themes/tempra-simple/public/main.css +++ b/themes/tempra-simple/public/main.css @@ -141,7 +141,9 @@ li a { padding: 0px; padding-top: 0px; } -.rowblock:empty { display: none; } +.rowblock:empty { + display: none; +} .rowsmall { font-size:12px; } @@ -449,6 +451,18 @@ button.username { position: relative; top: -0.25px; } top: -2px; } +@media(min-width: 881px) { + .shrink_main { + float: left; + width: calc(75% - 12px); + } + .sidebar { + float: left; + width: 25%; + margin-left: 12px; + } +} + @media (max-width: 880px) { li { height: 29px; @@ -477,6 +491,7 @@ button.username { position: relative; top: -0.25px; } overflow-x: hidden; } .container { width: auto; } + .sidebar { display: none; } .selectedAlert .alertList { top: 37px; right: 4px; } } diff --git a/themes/tempra-simple/public/sample.css b/themes/tempra-simple/public/sample.css new file mode 100644 index 00000000..8836f2ef --- /dev/null +++ b/themes/tempra-simple/public/sample.css @@ -0,0 +1 @@ +/* Sample CSS file injected by Tempra Simple. Doesn't do anything. */ diff --git a/themes/tempra-simple/theme.json b/themes/tempra-simple/theme.json index d15ad0e8..e8c2c4a6 100644 --- a/themes/tempra-simple/theme.json +++ b/themes/tempra-simple/theme.json @@ -5,5 +5,12 @@ "Creator": "Azareal", "FullImage": "tempra-simple.png", "MobileFriendly": true, - "URL": "github.com/Azareal/Gosora" + "URL": "github.com/Azareal/Gosora", + "Sidebars":"false", + "Resources": [ + { + "Name": "sample.css", + "Location": "none" + } + ] } diff --git a/user.go b/user.go index 83b6db08..08f3151b 100644 --- a/user.go +++ b/user.go @@ -2,9 +2,11 @@ package main import ( //"fmt" + "strings" "strconv" "net" "net/http" + "html/template" "golang.org/x/crypto/bcrypt" "database/sql" _ "github.com/go-sql-driver/mysql" @@ -80,11 +82,12 @@ func SendValidationEmail(username string, email string, token string) bool { } func SimpleForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (user User, success bool) { + user, success = SimpleSessionCheck(w,r) if !forum_exists(fid) { PreError("The target forum doesn't exist.",w,r) return user, false } - user, success = SimpleSessionCheck(w,r) + fperms := groups[user.Group].Forums[fid] if fperms.Overrides && !user.Is_Super_Admin { user.Perms.ViewTopic = fperms.ViewTopic @@ -108,11 +111,12 @@ func SimpleForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (u } func ForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (user User, headerVars HeaderVars, success bool) { + user, headerVars, success = SessionCheck(w,r) if !forum_exists(fid) { NotFound(w,r) return user, headerVars, false } - user, success = SimpleSessionCheck(w,r) + fperms := groups[user.Group].Forums[fid] //fmt.Printf("%+v\n", user.Perms) //fmt.Printf("%+v\n", fperms) @@ -134,9 +138,6 @@ func ForumSessionCheck(w http.ResponseWriter, r *http.Request, fid int) (user Us } } } - if user.Is_Banned { - headerVars.NoticeList = append(headerVars.NoticeList,"Your account has been suspended. Some of your permissions may have been revoked.") - } return user, headerVars, success } @@ -147,7 +148,25 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request) (user User, he NoPermissions(w,r,user) return user, headerVars, false } - headerVars.Stylesheets = append(headerVars.Stylesheets,"panel") + + headerVars.Stylesheets = append(headerVars.Stylesheets,"panel.css") + if len(themes[defaultTheme].Resources) != 0 { + rlist := themes[defaultTheme].Resources + for _, resource := range rlist { + if resource.Location == "global" || resource.Location == "panel" { + halves := strings.Split(resource.Name,".") + if len(halves) != 2 { + continue + } + if halves[1] == "css" { + headerVars.Stylesheets = append(headerVars.Stylesheets,resource.Name) + } else if halves[1] == "js" { + headerVars.Scripts = append(headerVars.Scripts,resource.Name) + } + } + } + } + return user, headerVars, success } func _simple_panel_session_check(w http.ResponseWriter, r *http.Request) (user User, success bool) { @@ -164,6 +183,40 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, headerVars if user.Is_Banned { headerVars.NoticeList = append(headerVars.NoticeList,"Your account has been suspended. Some of your permissions may have been revoked.") } + + if len(themes[defaultTheme].Resources) != 0 { + rlist := themes[defaultTheme].Resources + for _, resource := range rlist { + if resource.Location == "global" || resource.Location == "frontend" { + halves := strings.Split(resource.Name,".") + if len(halves) != 2 { + continue + } + if halves[1] == "css" { + headerVars.Stylesheets = append(headerVars.Stylesheets,resource.Name) + } else if halves[1] == "js" { + headerVars.Scripts = append(headerVars.Scripts,resource.Name) + } + } + } + } + + // TO-DO: Support for left sidebars and sidebars on both sides + //fmt.Println("themes[defaultTheme].Sidebars",themes[defaultTheme].Sidebars) + if themes[defaultTheme].Sidebars == "right" { + if len(docks.RightSidebar) != 0 { + var sbody string + for _, widget := range docks.RightSidebar { + //fmt.Println("widget",widget) + if widget.Enabled && widget.Location == "global" { + sbody += widget.Body + //fmt.Println("sbody",sbody) + } + } + headerVars.Sidebars.Right = template.HTML(sbody) + } + } + return user, headerVars, success } diff --git a/utils.go b/utils.go index 0a85dd6a..af48b138 100644 --- a/utils.go +++ b/utils.go @@ -5,6 +5,7 @@ import ( "time" "os" "math" + "errors" "strings" "unicode" "strconv" @@ -171,6 +172,57 @@ func SendEmail(email string, subject string, msg string) (res bool) { return true } +func weak_password(password string) error { + if len(password) < 8 { + return errors.New("Your password needs to be at-least eight characters long.") + } + var charMap map[rune]int = make(map[rune]int) + var numbers, /*letters, */symbols, upper, lower int + for _, char := range password { + charItem, ok := charMap[char] + if ok { + charItem++ + } else { + charItem = 1 + } + charMap[char] = charItem + + if unicode.IsLetter(char) { + //letters++ + if unicode.IsUpper(char) { + upper++ + } else { + lower++ + } + } else if unicode.IsNumber(char) { + numbers++ + } else { + symbols++ + } + } + + if numbers == 0 { + return errors.New("You don't have any numbers in your password.") + } + /*if letters == 0 { + return errors.New("You don't have any letters in your password.") + }*/ + if upper == 0 { + return errors.New("You don't have any uppercase characters in your password.") + } + if lower == 0 { + return errors.New("You don't have any lowercase characters in your password.") + } + if (len(password) / 2) < len(charMap) { + return errors.New("You don't have enough unique characters in your password.") + } + + if strings.Contains(strings.ToLower(password),"test") || /*strings.Contains(strings.ToLower(password),"123456") || */strings.Contains(strings.ToLower(password),"123") || strings.Contains(strings.ToLower(password),"password") || strings.Contains(strings.ToLower(password),"qwerty") { + return errors.New("You may not have 'test', '123', 'password' or 'qwerty' in your password.") + } + return nil +} + func write_file(name string, content string) (err error) { f, err := os.Create(name) if err != nil { diff --git a/widgets.go b/widgets.go new file mode 100644 index 00000000..9935b32e --- /dev/null +++ b/widgets.go @@ -0,0 +1,95 @@ +/* Copyright Azareal 2017 - 2018 */ +package main + +import "fmt" +import "bytes" +import "sync" +import "encoding/json" +//import "html/template" + +var docks WidgetDocks +var widget_update_mutex sync.RWMutex + +type WidgetDocks struct +{ + LeftSidebar []Widget + RightSidebar []Widget + //PanelLeft []Menus +} + +type Widget struct +{ + Enabled bool + Location string // Coming Soon: overview, topics, topic / topic_view, forums, forum, global + Position int + Body string +} + +type NameTextPair struct +{ + Name string + Text string +} + +func init_widgets() error { + rows, err := get_widgets_stmt.Query() + if err != nil { + return err + } + defer rows.Close() + + var sbytes []byte + var side, wtype, data string + + var leftWidgets []Widget + var rightWidgets []Widget + + for rows.Next() { + var widget Widget + err = rows.Scan(&widget.Position, &side, &wtype, &widget.Enabled, &widget.Location, &data) + if err != nil { + return err + } + + sbytes = []byte(data) + switch(wtype) { + case "simple": + var tmp NameTextPair + err = json.Unmarshal(sbytes, &tmp) + if err != nil { + return err + } + + var b bytes.Buffer + err = templates.ExecuteTemplate(&b,"widget_simple.html",tmp) + if err != nil { + return err + } + widget.Body = string(b.Bytes()) + default: + widget.Body = data + } + + if side == "left" { + leftWidgets = append(leftWidgets,widget) + } else if side == "right" { + rightWidgets = append(rightWidgets,widget) + } + } + err = rows.Err() + if err != nil { + return err + } + + widget_update_mutex.Lock() + docks.LeftSidebar = leftWidgets + docks.RightSidebar = rightWidgets + widget_update_mutex.Unlock() + + if super_debug { + fmt.Println("docks.LeftSidebar",docks.LeftSidebar) + fmt.Println("docks.RightSidebar",docks.RightSidebar) + } + + return nil +}