From 01a692ab5b1ba43e75bcad893ac172e8aefdda18 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sat, 4 Aug 2018 21:46:36 +1000 Subject: [PATCH] Added the word filter store and moved the word filter routes into the route package. Added tests for the word filter store. Added qgen.NewAcc() to reduce the amount of boilerplate needed for creating an accumulator. Exposed the RecordError method on the accumulator. Added an Add method to PluginList and removed AddPlugin() in favour of that. More panel buttons on Nox should be styled now. Added the panel_update_button_text phrase for future use. More errors might be caught in the thumbnailer now. Removed ls from .travis.yml, it was there for debugging Code Climate. --- .travis.yml | 1 - common/attachments.go | 2 +- common/auth.go | 2 +- common/common.go | 3 +- common/counters/agents.go | 2 +- common/counters/forums.go | 2 +- common/counters/langs.go | 2 +- common/counters/posts.go | 2 +- common/counters/referrers.go | 2 +- common/counters/routes.go | 2 +- common/counters/systems.go | 2 +- common/counters/topics.go | 2 +- common/counters/topics_views.go | 2 +- common/extend.go | 44 ++++------ common/forum_perms_store.go | 2 +- common/forum_store.go | 4 +- common/group_store.go | 2 +- common/ip_search.go | 2 +- common/menu_store.go | 3 +- common/module_ottojs.go | 18 ++-- common/parser.go | 6 +- common/poll_store.go | 5 +- common/routes_common.go | 4 +- common/statistics.go | 3 +- common/subscription.go | 2 +- common/tasks.go | 3 +- common/thumbnailer.go | 7 +- common/topic_store.go | 2 +- common/user_store.go | 5 +- common/word_filters.go | 115 ++++++++++++++++++++------ extend/guilds/lib/guild_store.go | 2 +- gen_mssql.go | 27 ------ gen_mysql.go | 24 ------ gen_pgsql.go | 16 ---- gen_router.go | 40 ++++----- langs/english.json | 1 + main.go | 8 +- misc_test.go | 27 ++++++ panel_routes.go | 129 ----------------------------- patcher/utils.go | 3 +- plugin_adventure.go | 11 ++- plugin_bbcode.go | 2 +- plugin_guilds.go | 6 +- plugin_heythere.go | 2 +- plugin_markdown.go | 2 +- plugin_skeleton.go | 2 +- query_gen/lib/accumulator.go | 21 +++-- query_gen/main.go | 6 -- router_gen/routes.go | 10 +-- routes/account.go | 6 +- routes/panel/analytics.go | 45 ++++------ routes/panel/word_filters.go | 133 ++++++++++++++++++++++++++++++ routes/poll.go | 3 +- routes/topic.go | 2 +- templates/panel_word_filters.html | 6 +- themes/nox/public/main.css | 4 +- themes/nox/public/panel.css | 13 ++- 57 files changed, 414 insertions(+), 390 deletions(-) create mode 100644 routes/panel/word_filters.go diff --git a/.travis.yml b/.travis.yml index b051cb54..abdc6078 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,6 @@ before_script: - ./cc-test-reporter before-build script: ./run-linux-tests after_script: - - ls - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT addons: mariadb: '10.0' \ No newline at end of file diff --git a/common/attachments.go b/common/attachments.go index 5d419022..8a73857b 100644 --- a/common/attachments.go +++ b/common/attachments.go @@ -17,7 +17,7 @@ type DefaultAttachmentStore struct { } func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &DefaultAttachmentStore{ add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(), }, acc.FirstError() diff --git a/common/auth.go b/common/auth.go index aa0c4c48..46a452e3 100644 --- a/common/auth.go +++ b/common/auth.go @@ -85,7 +85,7 @@ type DefaultAuth struct { // NewDefaultAuth is a factory for spitting out DefaultAuths func NewDefaultAuth() (*DefaultAuth, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &DefaultAuth{ login: acc.Select("users").Columns("uid, password, salt").Where("name = ?").Prepare(), logout: acc.Update("users").Set("session = ''").Where("uid = ?").Prepare(), diff --git a/common/common.go b/common/common.go index 523859db..87ee475b 100644 --- a/common/common.go +++ b/common/common.go @@ -83,8 +83,7 @@ var DbInits dbInits func (inits dbInits) Run() error { for _, init := range inits { - acc := qgen.Builder.Accumulator() - err := init(acc) + err := init(qgen.NewAcc()) if err != nil { return err } diff --git a/common/counters/agents.go b/common/counters/agents.go index 2f2f2008..e3d1c759 100644 --- a/common/counters/agents.go +++ b/common/counters/agents.go @@ -15,7 +15,7 @@ type DefaultAgentViewCounter struct { } func NewDefaultAgentViewCounter() (*DefaultAgentViewCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() var agentBuckets = make([]*RWMutexCounterBucket, len(agentMapEnum)) for bucketID, _ := range agentBuckets { agentBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} diff --git a/common/counters/forums.go b/common/counters/forums.go index 57f815dd..c33cf39b 100644 --- a/common/counters/forums.go +++ b/common/counters/forums.go @@ -22,7 +22,7 @@ type DefaultForumViewCounter struct { } func NewDefaultForumViewCounter() (*DefaultForumViewCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() counter := &DefaultForumViewCounter{ oddMap: make(map[int]*RWMutexCounterBucket), evenMap: make(map[int]*RWMutexCounterBucket), diff --git a/common/counters/langs.go b/common/counters/langs.go index 4b07a0bb..e1ee7c5e 100644 --- a/common/counters/langs.go +++ b/common/counters/langs.go @@ -100,7 +100,7 @@ type DefaultLangViewCounter struct { } func NewDefaultLangViewCounter() (*DefaultLangViewCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() var langBuckets = make([]*RWMutexCounterBucket, len(langCodes)) for bucketID, _ := range langBuckets { diff --git a/common/counters/posts.go b/common/counters/posts.go index c4a8ae45..7df259c9 100644 --- a/common/counters/posts.go +++ b/common/counters/posts.go @@ -18,7 +18,7 @@ type DefaultPostCounter struct { } func NewPostCounter() (*DefaultPostCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() counter := &DefaultPostCounter{ currentBucket: 0, insert: acc.Insert("postchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(), diff --git a/common/counters/referrers.go b/common/counters/referrers.go index f9c7d5b6..6aef587f 100644 --- a/common/counters/referrers.go +++ b/common/counters/referrers.go @@ -30,7 +30,7 @@ type DefaultReferrerTracker struct { } func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() refTracker := &DefaultReferrerTracker{ odd: make(map[string]*ReferrerItem), even: make(map[string]*ReferrerItem), diff --git a/common/counters/routes.go b/common/counters/routes.go index ee399845..8e8863d8 100644 --- a/common/counters/routes.go +++ b/common/counters/routes.go @@ -13,7 +13,7 @@ type DefaultRouteViewCounter struct { } func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum)) for bucketID, _ := range routeBuckets { routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} diff --git a/common/counters/systems.go b/common/counters/systems.go index c25f5734..636a65b4 100644 --- a/common/counters/systems.go +++ b/common/counters/systems.go @@ -12,7 +12,7 @@ type DefaultOSViewCounter struct { } func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() var osBuckets = make([]*RWMutexCounterBucket, len(osMapEnum)) for bucketID, _ := range osBuckets { osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} diff --git a/common/counters/topics.go b/common/counters/topics.go index 3387e01b..ae913800 100644 --- a/common/counters/topics.go +++ b/common/counters/topics.go @@ -18,7 +18,7 @@ type DefaultTopicCounter struct { } func NewTopicCounter() (*DefaultTopicCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() counter := &DefaultTopicCounter{ currentBucket: 0, insert: acc.Insert("topicchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(), diff --git a/common/counters/topics_views.go b/common/counters/topics_views.go index 5a227dfe..8de55e79 100644 --- a/common/counters/topics_views.go +++ b/common/counters/topics_views.go @@ -21,7 +21,7 @@ type DefaultTopicViewCounter struct { } func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() counter := &DefaultTopicViewCounter{ oddTopics: make(map[int]*RWMutexCounterBucket), evenTopics: make(map[int]*RWMutexCounterBucket), diff --git a/common/extend.go b/common/extend.go index 7a43d665..9ca31050 100644 --- a/common/extend.go +++ b/common/extend.go @@ -6,6 +6,7 @@ */ package common +// TODO: Break this file up into multiple files to make it easier to maintain import ( "database/sql" "errors" @@ -22,6 +23,22 @@ type PluginList map[string]*Plugin // TODO: Have a proper store rather than a map? var Plugins PluginList = make(map[string]*Plugin) +func (list PluginList) Add(plugin *Plugin) { + buildPlugin(plugin) + list[plugin.UName] = plugin +} + +func buildPlugin(plugin *Plugin) { + plugin.Installable = (plugin.Install != nil) + /* + 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. + */ + plugin.Active = false + plugin.Installed = false + plugin.Hooks = make(map[string]int) + plugin.Data = nil +} + // Hooks with a single argument. Is this redundant? Might be useful for inlining, as variadics aren't inlined? Are closures even inlined to begin with? var Hooks = map[string][]func(interface{}) interface{}{ "forums_frow_assign": nil, @@ -158,7 +175,7 @@ type Plugin struct { Activate func() error Deactivate func() // TODO: We might want to let this return an error? Install func() error - Uninstall func() error + Uninstall func() error // TODO: I'm not sure uninstall is implemented Hooks map[string]int Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins @@ -272,31 +289,6 @@ func (plugins PluginList) Load() error { return rows.Err() } -func NewPlugin(uname string, name string, author string, url string, settings string, tag string, ptype string, init func() error, activate func() error, deactivate func(), install func() error, uninstall func() error) *Plugin { - return &Plugin{ - UName: uname, - Name: name, - Author: author, - URL: url, - Settings: settings, - Tag: tag, - Type: ptype, - Installable: (install != nil), - Init: init, - Activate: activate, - Deactivate: deactivate, - Install: install, - //Uninstall: uninstall, - - /* - 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. - */ - Active: false, - Installed: false, - Hooks: make(map[string]int), - } -} - // ? - Is this racey? // TODO: Generate the cases in this switch func (plugin *Plugin) AddHook(name string, handler interface{}) { diff --git a/common/forum_perms_store.go b/common/forum_perms_store.go index e6fcbf3e..9eb484cf 100644 --- a/common/forum_perms_store.go +++ b/common/forum_perms_store.go @@ -33,7 +33,7 @@ type MemoryForumPermsStore struct { } func NewMemoryForumPermsStore() (*MemoryForumPermsStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &MemoryForumPermsStore{ getByForum: acc.Select("forums_permissions").Columns("gid, permissions").Where("fid = ?").Orderby("gid ASC").Prepare(), getByForumGroup: acc.Select("forums_permissions").Columns("permissions").Where("fid = ? AND gid = ?").Prepare(), diff --git a/common/forum_store.go b/common/forum_store.go index 2905545c..4307e5d7 100644 --- a/common/forum_store.go +++ b/common/forum_store.go @@ -70,7 +70,7 @@ type MemoryForumStore struct { // NewMemoryForumStore gives you a new instance of MemoryForumStore func NewMemoryForumStore() (*MemoryForumStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() // TODO: Do a proper delete return &MemoryForumStore{ get: acc.Select("forums").Columns("name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid = ?").Prepare(), @@ -354,7 +354,7 @@ func (mfs *MemoryForumStore) Length() (length int) { return length } -// TODO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this? +// TODO: Get the total count of forums in the forum store rather than doing a heavy query for this? // GlobalCount returns the total number of forums func (mfs *MemoryForumStore) GlobalCount() (fcount int) { err := mfs.count.QueryRow().Scan(&fcount) diff --git a/common/group_store.go b/common/group_store.go index 2ba85057..e758db37 100644 --- a/common/group_store.go +++ b/common/group_store.go @@ -45,7 +45,7 @@ type MemoryGroupStore struct { } func NewMemoryGroupStore() (*MemoryGroupStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &MemoryGroupStore{ groups: make(map[int]*Group), groupCount: 0, diff --git a/common/ip_search.go b/common/ip_search.go index 4407db84..05776c22 100644 --- a/common/ip_search.go +++ b/common/ip_search.go @@ -21,7 +21,7 @@ type DefaultIPSearcher struct { // NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher func NewDefaultIPSearcher() (*DefaultIPSearcher, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &DefaultIPSearcher{ searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(), searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(), diff --git a/common/menu_store.go b/common/menu_store.go index a706be1c..87ac3c04 100644 --- a/common/menu_store.go +++ b/common/menu_store.go @@ -40,8 +40,7 @@ func (store *DefaultMenuStore) Get(mid int) (*MenuListHolder, error) { } func (store *DefaultMenuStore) Items(mid int) (mlist MenuItemList, err error) { - acc := qgen.Builder.Accumulator() - err = acc.Select("menu_items").Columns("miid, name, htmlID, cssClass, position, path, aria, tooltip, order, tmplName, guestOnly, memberOnly, staffOnly, adminOnly").Where("mid = " + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error { + err = qgen.NewAcc().Select("menu_items").Columns("miid, name, htmlID, cssClass, position, path, aria, tooltip, order, tmplName, guestOnly, memberOnly, staffOnly, adminOnly").Where("mid = " + strconv.Itoa(mid)).Orderby("order ASC").Each(func(rows *sql.Rows) error { var mitem = MenuItem{MenuID: mid} err := rows.Scan(&mitem.ID, &mitem.Name, &mitem.HTMLID, &mitem.CSSClass, &mitem.Position, &mitem.Path, &mitem.Aria, &mitem.Tooltip, &mitem.Order, &mitem.TmplName, &mitem.GuestOnly, &mitem.MemberOnly, &mitem.SuperModOnly, &mitem.AdminOnly) if err != nil { diff --git a/common/module_ottojs.go b/common/module_ottojs.go index fe2167cb..ed6c9f3e 100644 --- a/common/module_ottojs.go +++ b/common/module_ottojs.go @@ -61,12 +61,20 @@ func (js *OttoPluginLang) AddPlugin(meta PluginMeta) (plugin *Plugin, err error) } return nil } - var pluginActivate func() error - var pluginDeactivate func() - var pluginInstall func() error - var pluginUninstall func() error - plugin = NewPlugin(meta.UName, meta.Name, meta.Author, meta.URL, meta.Settings, meta.Tag, "ottojs", pluginInit, pluginActivate, pluginDeactivate, pluginInstall, pluginUninstall) + plugin = new(Plugin) + plugin.UName = meta.UName + plugin.Name = meta.Name + plugin.Author = meta.Author + plugin.URL = meta.URL + plugin.Settings = meta.Settings + plugin.Tag = meta.Tag + plugin.Type = "ottojs" + plugin.Init = pluginInit + + // TODO: Implement plugin life cycle events + + buildPlugin(plugin) plugin.Data = script return plugin, nil diff --git a/common/parser.go b/common/parser.go index b4fdfc60..2b58c587 100644 --- a/common/parser.go +++ b/common/parser.go @@ -413,7 +413,11 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/) //msg = url_reg.ReplaceAllString(msg,"$2$3//$4") // Word filter list. E.g. Swear words and other things the admins don't like - wordFilters := WordFilterBox.Load().(WordFilterMap) + wordFilters, err := WordFilters.GetAll() + if err != nil { + LogError(err) + return "" + } for _, filter := range wordFilters { msg = strings.Replace(msg, filter.Find, filter.Replacement, -1) } diff --git a/common/poll_store.go b/common/poll_store.go index 2e2a3985..5790952a 100644 --- a/common/poll_store.go +++ b/common/poll_store.go @@ -72,7 +72,7 @@ type DefaultPollStore struct { } func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() if cache == nil { cache = NewNullPollCache() } @@ -153,8 +153,7 @@ func (store *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err er } qlist = qlist[0 : len(qlist)-1] - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("polls").Columns("pollID, parentID, parentTable, type, options, votes").Where("pollID IN(" + qlist + ")").Query(pollIDList...) + rows, err := qgen.NewAcc().Select("polls").Columns("pollID, parentID, parentTable, type, options, votes").Where("pollID IN(" + qlist + ")").Query(pollIDList...) if err != nil { return list, err } diff --git a/common/routes_common.go b/common/routes_common.go index 00d65c58..3a48734c 100644 --- a/common/routes_common.go +++ b/common/routes_common.go @@ -141,10 +141,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header // TODO: GDPR. Add a global control panel notice warning the admins of staff members who don't have 2FA enabled stats.Users = Users.GlobalCount() stats.Groups = Groups.GlobalCount() - stats.Forums = Forums.GlobalCount() // TODO: Stop it from showing the blanked forums, do we still have those? I think we removed that + stats.Forums = Forums.GlobalCount() stats.Pages = Pages.GlobalCount() stats.Settings = len(header.Settings) - stats.WordFilters = len(WordFilterBox.Load().(WordFilterMap)) + stats.WordFilters = WordFilters.EstCount() stats.Themes = len(Themes) stats.Reports = 0 // TODO: Do the report count. Only show open threads? diff --git a/common/statistics.go b/common/statistics.go index 84633922..241b4b10 100644 --- a/common/statistics.go +++ b/common/statistics.go @@ -27,8 +27,7 @@ func (store *DefaultStatStore) LookupInt(name string, duration int, unit string) } func (store *DefaultStatStore) countTable(table string, duration int, unit string) (stat int, err error) { - /*acc := qgen.Builder.Accumulator() - counter := acc.Count("replies").DateCutoff("createdAt", 1, "day").Prepare() + /*counter := qgen.NewAcc().Count("replies").DateCutoff("createdAt", 1, "day").Prepare() if acc.FirstError() != nil { return 0, acc.FirstError() } diff --git a/common/subscription.go b/common/subscription.go index 8ad83715..146c2be4 100644 --- a/common/subscription.go +++ b/common/subscription.go @@ -15,7 +15,7 @@ type DefaultSubscriptionStore struct { } func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &DefaultSubscriptionStore{ add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(), }, acc.FirstError() diff --git a/common/tasks.go b/common/tasks.go index 00902ceb..f367eee4 100644 --- a/common/tasks.go +++ b/common/tasks.go @@ -93,6 +93,7 @@ func HandleExpiredScheduledGroups() error { // TODO: Use AddScheduledSecondTask // TODO: Be a little more granular with the synchronisation // TODO: Synchronise more things +// TODO: Does this even work? func HandleServerSync() error { // We don't want to run any unnecessary queries when there is nothing to synchronise /*if Config.ServerCount > 1 { @@ -118,7 +119,7 @@ func HandleServerSync() error { log.Print("Unable to reload the settings") return err } - err = LoadWordFilters() + err = WordFilters.ReloadAll() if err != nil { log.Print("Unable to reload the word filters") return err diff --git a/common/thumbnailer.go b/common/thumbnailer.go index 4f5ee598..514a84a3 100644 --- a/common/thumbnailer.go +++ b/common/thumbnailer.go @@ -13,20 +13,18 @@ import ( ) func ThumbTask(thumbChan chan bool) { - acc := qgen.Builder.Accumulator() for { // Put this goroutine to sleep until we have work to do <-thumbChan // TODO: Use a real queue + acc := qgen.NewAcc() err := acc.Select("users_avatar_queue").Columns("uid").Limit("0,5").EachInt(func(uid int) error { - //log.Print("uid: ", uid) // TODO: Do a bulk user fetch instead? user, err := Users.Get(uid) if err != nil { return errors.WithStack(err) } - //log.Print("user.RawAvatar: ", user.RawAvatar) // Has the avatar been removed or already been processed by the thumbnailer? if len(user.RawAvatar) < 2 || user.RawAvatar[1] == '.' { @@ -59,6 +57,9 @@ func ThumbTask(thumbChan chan bool) { if err != nil { LogError(err) } + if err = acc.FirstError(); err != nil { + LogError(err) + } } } diff --git a/common/topic_store.go b/common/topic_store.go index 6c9279c2..69e03304 100644 --- a/common/topic_store.go +++ b/common/topic_store.go @@ -51,7 +51,7 @@ type DefaultTopicStore struct { // NewDefaultTopicStore gives you a new instance of DefaultTopicStore func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() if cache == nil { cache = NewNullTopicCache() } diff --git a/common/user_store.go b/common/user_store.go index 6d8cd8a2..485368f3 100644 --- a/common/user_store.go +++ b/common/user_store.go @@ -45,7 +45,7 @@ type DefaultUserStore struct { // NewDefaultUserStore gives you a new instance of DefaultUserStore func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() if cache == nil { cache = NewNullUserCache() } @@ -154,8 +154,7 @@ func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err erro } qlist = qlist[0 : len(qlist)-1] - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Where("uid IN(" + qlist + ")").Query(uidList...) + rows, err := qgen.NewAcc().Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Where("uid IN(" + qlist + ")").Query(uidList...) if err != nil { return list, err } diff --git a/common/word_filters.go b/common/word_filters.go index c0a440f8..831d1921 100644 --- a/common/word_filters.go +++ b/common/word_filters.go @@ -7,34 +7,55 @@ import ( "../query_gen/lib" ) +// TODO: Move some features into methods on this? type WordFilter struct { ID int Find string Replacement string } -type WordFilterMap map[int]WordFilter -var WordFilterBox atomic.Value // An atomic value holding a WordFilterBox +var WordFilters WordFilterStore -type FilterStmts struct { - getWordFilters *sql.Stmt +type WordFilterStore interface { + ReloadAll() error + GetAll() (filters map[int]*WordFilter, err error) + Create(find string, replacement string) error + Delete(id int) error + Update(id int, find string, replacement string) error + Length() int + EstCount() int + GlobalCount() (count int) } -var filterStmts FilterStmts +type DefaultWordFilterStore struct { + box atomic.Value // An atomic value holding a WordFilterMap -func init() { - WordFilterBox.Store(WordFilterMap(make(map[int]WordFilter))) - DbInits.Add(func(acc *qgen.Accumulator) error { - filterStmts = FilterStmts{ - getWordFilters: acc.Select("word_filters").Columns("wfid, find, replacement").Prepare(), - } - return acc.FirstError() - }) + getAll *sql.Stmt + create *sql.Stmt + delete *sql.Stmt + update *sql.Stmt + count *sql.Stmt } -func LoadWordFilters() error { - var wordFilters = WordFilterMap(make(map[int]WordFilter)) - filters, err := wordFilters.BypassGetAll() +func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore, error) { + store := &DefaultWordFilterStore{ + getAll: acc.Select("word_filters").Columns("wfid, find, replacement").Prepare(), + create: acc.Insert("word_filters").Columns("find, replacement").Fields("?,?").Prepare(), + delete: acc.Delete("word_filters").Where("wfid = ?").Prepare(), + update: acc.Update("word_filters").Set("find = ?, replacement = ?").Where("wfid = ?").Prepare(), + count: acc.Count("word_filters").Prepare(), + } + // TODO: Should we initialise this elsewhere? + if acc.FirstError() == nil { + acc.RecordError(store.ReloadAll()) + } + return store, acc.FirstError() +} + +// ReloadAll drops all the items in the memory cache and replaces them with fresh copies from the database +func (store *DefaultWordFilterStore) ReloadAll() error { + var wordFilters = make(map[int]*WordFilter) + filters, err := store.bypassGetAll() if err != nil { return err } @@ -43,20 +64,20 @@ func LoadWordFilters() error { wordFilters[filter.ID] = filter } - WordFilterBox.Store(wordFilters) + store.box.Store(wordFilters) return nil } -// TODO: Return pointers to word filters intead to save memory? -func (wBox WordFilterMap) BypassGetAll() (filters []WordFilter, err error) { - rows, err := filterStmts.getWordFilters.Query() +// ? - Return pointers to word filters intead to save memory? -- A map is a pointer. +func (store *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) { + rows, err := store.getAll.Query() if err != nil { return nil, err } defer rows.Close() for rows.Next() { - filter := WordFilter{ID: 0} + filter := &WordFilter{ID: 0} err := rows.Scan(&filter.ID, &filter.Find, &filter.Replacement) if err != nil { return filters, err @@ -66,8 +87,52 @@ func (wBox WordFilterMap) BypassGetAll() (filters []WordFilter, err error) { return filters, rows.Err() } -func AddWordFilter(id int, find string, replacement string) { - wordFilters := WordFilterBox.Load().(WordFilterMap) - wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement} - WordFilterBox.Store(wordFilters) +// GetAll returns all of the word filters in a map. Do note mutate this map (or maps returned from any store not explicitly noted as copies) as multiple threads may be accessing it at once +func (store *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) { + return store.box.Load().(map[int]*WordFilter), nil +} + +// Create adds a new word filter to the database and refreshes the memory cache +func (store *DefaultWordFilterStore) Create(find string, replacement string) error { + _, err := store.create.Exec(find, replacement) + if err != nil { + return err + } + return store.ReloadAll() +} + +// Delete removes a word filter from the database and refreshes the memory cache +func (store *DefaultWordFilterStore) Delete(id int) error { + _, err := store.delete.Exec(id) + if err != nil { + return err + } + return store.ReloadAll() +} + +func (store *DefaultWordFilterStore) Update(id int, find string, replacement string) error { + _, err := store.update.Exec(find, replacement, id) + if err != nil { + return err + } + return store.ReloadAll() +} + +// Length gets the number of word filters currently in memory, for the DefaultWordFilterStore, this should be all of them +func (store *DefaultWordFilterStore) Length() int { + return len(store.box.Load().(map[int]*WordFilter)) +} + +// EstCount provides the same result as Length(), intended for alternate implementations of WordFilterStore, so that Length is the number of items in cache, if only a subset is held there and EstCount is the total count +func (store *DefaultWordFilterStore) EstCount() int { + return len(store.box.Load().(map[int]*WordFilter)) +} + +// GlobalCount gets the total number of word filters directly from the database +func (store *DefaultWordFilterStore) GlobalCount() (count int) { + err := store.count.QueryRow().Scan(&count) + if err != nil { + LogError(err) + } + return count } diff --git a/extend/guilds/lib/guild_store.go b/extend/guilds/lib/guild_store.go index 018555f8..6490b71d 100644 --- a/extend/guilds/lib/guild_store.go +++ b/extend/guilds/lib/guild_store.go @@ -16,7 +16,7 @@ type SQLGuildStore struct { } func NewSQLGuildStore() (*SQLGuildStore, error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() return &SQLGuildStore{ get: acc.Select("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Where("guildID = ?").Prepare(), create: acc.Insert("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Fields("?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(), diff --git a/gen_mssql.go b/gen_mssql.go index 922c9d7a..b43ed5e5 100644 --- a/gen_mssql.go +++ b/gen_mssql.go @@ -15,16 +15,13 @@ type Stmts struct { getForumTopics *sql.Stmt addForumPermsToForum *sql.Stmt addTheme *sql.Stmt - createWordFilter *sql.Stmt updateTheme *sql.Stmt updateGroupPerms *sql.Stmt updateGroup *sql.Stmt updateEmail *sql.Stmt setTempGroup *sql.Stmt - updateWordFilter *sql.Stmt bumpSync *sql.Stmt deleteActivityStreamMatch *sql.Stmt - deleteWordFilter *sql.Stmt getActivityFeedByWatcher *sql.Stmt getActivityCountByWatcher *sql.Stmt @@ -88,14 +85,6 @@ func _gen_mssql() (err error) { return err } - common.DebugLog("Preparing createWordFilter statement.") - stmts.createWordFilter, err = db.Prepare("INSERT INTO [word_filters] ([find],[replacement]) VALUES (?,?)") - if err != nil { - log.Print("Error in createWordFilter statement.") - log.Print("Bad Query: ","INSERT INTO [word_filters] ([find],[replacement]) VALUES (?,?)") - return err - } - common.DebugLog("Preparing updateTheme statement.") stmts.updateTheme, err = db.Prepare("UPDATE [themes] SET [default] = ? WHERE [uname] = ?") if err != nil { @@ -136,14 +125,6 @@ func _gen_mssql() (err error) { return err } - common.DebugLog("Preparing updateWordFilter statement.") - stmts.updateWordFilter, err = db.Prepare("UPDATE [word_filters] SET [find] = ?,[replacement] = ? WHERE [wfid] = ?") - if err != nil { - log.Print("Error in updateWordFilter statement.") - log.Print("Bad Query: ","UPDATE [word_filters] SET [find] = ?,[replacement] = ? WHERE [wfid] = ?") - return err - } - common.DebugLog("Preparing bumpSync statement.") stmts.bumpSync, err = db.Prepare("UPDATE [sync] SET [last_update] = GETUTCDATE()") if err != nil { @@ -159,14 +140,6 @@ func _gen_mssql() (err error) { log.Print("Bad Query: ","DELETE FROM [activity_stream_matches] WHERE [watcher] = ? AND [asid] = ?") return err } - - common.DebugLog("Preparing deleteWordFilter statement.") - stmts.deleteWordFilter, err = db.Prepare("DELETE FROM [word_filters] WHERE [wfid] = ?") - if err != nil { - log.Print("Error in deleteWordFilter statement.") - log.Print("Bad Query: ","DELETE FROM [word_filters] WHERE [wfid] = ?") - return err - } return nil } diff --git a/gen_mysql.go b/gen_mysql.go index 5e1cc495..28b4eab5 100644 --- a/gen_mysql.go +++ b/gen_mysql.go @@ -17,16 +17,13 @@ type Stmts struct { getForumTopics *sql.Stmt addForumPermsToForum *sql.Stmt addTheme *sql.Stmt - createWordFilter *sql.Stmt updateTheme *sql.Stmt updateGroupPerms *sql.Stmt updateGroup *sql.Stmt updateEmail *sql.Stmt setTempGroup *sql.Stmt - updateWordFilter *sql.Stmt bumpSync *sql.Stmt deleteActivityStreamMatch *sql.Stmt - deleteWordFilter *sql.Stmt getActivityFeedByWatcher *sql.Stmt getActivityCountByWatcher *sql.Stmt @@ -84,13 +81,6 @@ func _gen_mysql() (err error) { return err } - common.DebugLog("Preparing createWordFilter statement.") - stmts.createWordFilter, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)") - if err != nil { - log.Print("Error in createWordFilter statement.") - return err - } - common.DebugLog("Preparing updateTheme statement.") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") if err != nil { @@ -126,13 +116,6 @@ func _gen_mysql() (err error) { return err } - common.DebugLog("Preparing updateWordFilter statement.") - stmts.updateWordFilter, err = db.Prepare("UPDATE `word_filters` SET `find` = ?,`replacement` = ? WHERE `wfid` = ?") - if err != nil { - log.Print("Error in updateWordFilter statement.") - return err - } - common.DebugLog("Preparing bumpSync statement.") stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = UTC_TIMESTAMP()") if err != nil { @@ -146,13 +129,6 @@ func _gen_mysql() (err error) { log.Print("Error in deleteActivityStreamMatch statement.") return err } - - common.DebugLog("Preparing deleteWordFilter statement.") - stmts.deleteWordFilter, err = db.Prepare("DELETE FROM `word_filters` WHERE `wfid` = ?") - if err != nil { - log.Print("Error in deleteWordFilter statement.") - return err - } return nil } diff --git a/gen_pgsql.go b/gen_pgsql.go index cbff962d..cad5b5cd 100644 --- a/gen_pgsql.go +++ b/gen_pgsql.go @@ -11,13 +11,11 @@ import "./common" type Stmts struct { addForumPermsToForum *sql.Stmt addTheme *sql.Stmt - createWordFilter *sql.Stmt updateTheme *sql.Stmt updateGroupPerms *sql.Stmt updateGroup *sql.Stmt updateEmail *sql.Stmt setTempGroup *sql.Stmt - updateWordFilter *sql.Stmt bumpSync *sql.Stmt getActivityFeedByWatcher *sql.Stmt @@ -48,13 +46,6 @@ func _gen_pgsql() (err error) { return err } - common.DebugLog("Preparing createWordFilter statement.") - stmts.createWordFilter, err = db.Prepare("INSERT INTO "word_filters"("find","replacement") VALUES (?,?)") - if err != nil { - log.Print("Error in createWordFilter statement.") - return err - } - common.DebugLog("Preparing updateTheme statement.") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") if err != nil { @@ -90,13 +81,6 @@ func _gen_pgsql() (err error) { return err } - common.DebugLog("Preparing updateWordFilter statement.") - stmts.updateWordFilter, err = db.Prepare("UPDATE `word_filters` SET `find` = ?,`replacement` = ? WHERE `wfid` = ?") - if err != nil { - log.Print("Error in updateWordFilter statement.") - return err - } - common.DebugLog("Preparing bumpSync statement.") stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = LOCALTIMESTAMP()") if err != nil { diff --git a/gen_router.go b/gen_router.go index 7d6e9093..4e2de353 100644 --- a/gen_router.go +++ b/gen_router.go @@ -46,11 +46,11 @@ var RouteMap = map[string]interface{}{ "panel.Settings": panel.Settings, "panel.SettingEdit": panel.SettingEdit, "panel.SettingEditSubmit": panel.SettingEditSubmit, - "routePanelWordFilters": routePanelWordFilters, - "routePanelWordFiltersCreateSubmit": routePanelWordFiltersCreateSubmit, - "routePanelWordFiltersEdit": routePanelWordFiltersEdit, - "routePanelWordFiltersEditSubmit": routePanelWordFiltersEditSubmit, - "routePanelWordFiltersDeleteSubmit": routePanelWordFiltersDeleteSubmit, + "panel.WordFilters": panel.WordFilters, + "panel.WordFiltersCreateSubmit": panel.WordFiltersCreateSubmit, + "panel.WordFiltersEdit": panel.WordFiltersEdit, + "panel.WordFiltersEditSubmit": panel.WordFiltersEditSubmit, + "panel.WordFiltersDeleteSubmit": panel.WordFiltersDeleteSubmit, "panel.Pages": panel.Pages, "panel.PagesCreateSubmit": panel.PagesCreateSubmit, "panel.PagesEdit": panel.PagesEdit, @@ -175,11 +175,11 @@ var routeMapEnum = map[string]int{ "panel.Settings": 22, "panel.SettingEdit": 23, "panel.SettingEditSubmit": 24, - "routePanelWordFilters": 25, - "routePanelWordFiltersCreateSubmit": 26, - "routePanelWordFiltersEdit": 27, - "routePanelWordFiltersEditSubmit": 28, - "routePanelWordFiltersDeleteSubmit": 29, + "panel.WordFilters": 25, + "panel.WordFiltersCreateSubmit": 26, + "panel.WordFiltersEdit": 27, + "panel.WordFiltersEditSubmit": 28, + "panel.WordFiltersDeleteSubmit": 29, "panel.Pages": 30, "panel.PagesCreateSubmit": 31, "panel.PagesEdit": 32, @@ -302,11 +302,11 @@ var reverseRouteMapEnum = map[int]string{ 22: "panel.Settings", 23: "panel.SettingEdit", 24: "panel.SettingEditSubmit", - 25: "routePanelWordFilters", - 26: "routePanelWordFiltersCreateSubmit", - 27: "routePanelWordFiltersEdit", - 28: "routePanelWordFiltersEditSubmit", - 29: "routePanelWordFiltersDeleteSubmit", + 25: "panel.WordFilters", + 26: "panel.WordFiltersCreateSubmit", + 27: "panel.WordFiltersEdit", + 28: "panel.WordFiltersEditSubmit", + 29: "panel.WordFiltersDeleteSubmit", 30: "panel.Pages", 31: "panel.PagesCreateSubmit", 32: "panel.PagesEdit", @@ -1070,7 +1070,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { err = panel.SettingEditSubmit(w,req,user,extraData) case "/panel/settings/word-filters/": counters.RouteViewCounter.Bump(25) - err = routePanelWordFilters(w,req,user) + err = panel.WordFilters(w,req,user) case "/panel/settings/word-filters/create/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1079,10 +1079,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(26) - err = routePanelWordFiltersCreateSubmit(w,req,user) + err = panel.WordFiltersCreateSubmit(w,req,user) case "/panel/settings/word-filters/edit/": counters.RouteViewCounter.Bump(27) - err = routePanelWordFiltersEdit(w,req,user,extraData) + err = panel.WordFiltersEdit(w,req,user,extraData) case "/panel/settings/word-filters/edit/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1091,7 +1091,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(28) - err = routePanelWordFiltersEditSubmit(w,req,user,extraData) + err = panel.WordFiltersEditSubmit(w,req,user,extraData) case "/panel/settings/word-filters/delete/submit/": err = common.NoSessionMismatch(w,req,user) if err != nil { @@ -1100,7 +1100,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } counters.RouteViewCounter.Bump(29) - err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData) + err = panel.WordFiltersDeleteSubmit(w,req,user,extraData) case "/panel/pages/": err = common.AdminOnly(w,req,user) if err != nil { diff --git a/langs/english.json b/langs/english.json index 3ee514cf..8b1928c5 100644 --- a/langs/english.json +++ b/langs/english.json @@ -326,6 +326,7 @@ "panel_perms_default":"Default", "panel_edit_button_text":"Edit", + "panel_update_button_text":"Update", "panel_delete_button_text":"Delete", "menu_forums_tooltip":"Forum List", diff --git a/main.go b/main.go index 210303f9..1b3bc589 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,7 @@ type Globs struct { // Experimenting with a new error package here to try to reduce the amount of debugging we have to do // TODO: Dynamically register these items to avoid maintaining as much code here? func afterDBInit() (err error) { - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() common.Rstore, err = common.NewSQLReplyStore(acc) if err != nil { return errors.WithStack(err) @@ -102,13 +102,11 @@ func afterDBInit() (err error) { return errors.WithStack(err) } - log.Print("Loading the word filters") - err = common.LoadWordFilters() + log.Print("Initialising the stores") + common.WordFilters, err = common.NewDefaultWordFilterStore(acc) if err != nil { return errors.WithStack(err) } - - log.Print("Initialising the stores") common.MFAstore, err = common.NewSQLMFAStore(acc) if err != nil { return errors.WithStack(err) diff --git a/misc_test.go b/misc_test.go index a7893b7b..ce0663ac 100644 --- a/misc_test.go +++ b/misc_test.go @@ -980,6 +980,33 @@ func TestPhrases(t *testing.T) { // TODO: Cover the other phrase types, also try switching between languages to see if anything strange happens } +func TestWordFilters(t *testing.T) { + // TODO: Test the word filters and their store + expect(t, common.WordFilters.Length() == 0, "Word filter list should be empty") + expect(t, common.WordFilters.EstCount() == 0, "Word filter list should be empty") + expect(t, common.WordFilters.GlobalCount() == 0, "Word filter list should be empty") + filters, err := common.WordFilters.GetAll() + expectNilErr(t, err) // TODO: Slightly confusing that we don't get ErrNoRow here + expect(t, len(filters) == 0, "Word filter map should be empty") + // TODO: Add a test for ParseMessage relating to word filters + + err = common.WordFilters.Create("imbecile", "lovely") + expectNilErr(t, err) + expect(t, common.WordFilters.Length() == 1, "Word filter list should not be empty") + expect(t, common.WordFilters.EstCount() == 1, "Word filter list should not be empty") + expect(t, common.WordFilters.GlobalCount() == 1, "Word filter list should not be empty") + filters, err = common.WordFilters.GetAll() + expectNilErr(t, err) + expect(t, len(filters) == 1, "Word filter map should not be empty") + filter := filters[1] + expect(t, filter.ID == 1, "Word filter ID should be 1") + expect(t, filter.Find == "imbecile", "Word filter needle should be imbecile") + expect(t, filter.Replacement == "lovely", "Word filter replacement should be lovely") + // TODO: Add a test for ParseMessage relating to word filters + + // TODO: Add deletion tests +} + func TestSlugs(t *testing.T) { var res string var msgList []MEPair diff --git a/panel_routes.go b/panel_routes.go index b3bf9294..e4073705 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -171,135 +171,6 @@ func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.Use return panelRenderTemplate("panel_dashboard", w, r, user, &pi) } -func routePanelWordFilters(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - header, stats, ferr := common.PanelUserCheck(w, r, &user) - if ferr != nil { - return ferr - } - if !user.Perms.EditSettings { - return common.NoPermissions(w, r, user) - } - header.Title = common.GetTitlePhrase("panel_word_filters") - - var filterList = common.WordFilterBox.Load().(common.WordFilterMap) - pi := common.PanelPage{&common.BasePanelPage{header, stats, "word-filters", common.ReportForumID}, tList, filterList} - return panelRenderTemplate("panel_word_filters", w, r, user, &pi) -} - -func routePanelWordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { - _, ferr := common.SimplePanelUserCheck(w, r, &user) - if ferr != nil { - return ferr - } - if !user.Perms.EditSettings { - return common.NoPermissions(w, r, user) - } - isJs := (r.PostFormValue("js") == "1") - - // ? - We're not doing a full sanitise here, as it would be useful if admins were able to put down rules for replacing things with HTML, etc. - find := strings.TrimSpace(r.PostFormValue("find")) - if find == "" { - return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) - } - - // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement - replacement := strings.TrimSpace(r.PostFormValue("replacement")) - - res, err := stmts.createWordFilter.Exec(find, replacement) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - lastID, err := res.LastInsertId() - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - common.AddWordFilter(int(lastID), find, replacement) - return panelSuccessRedirect("/panel/settings/word-filters/", w, r, isJs) -} - -// TODO: Implement this as a non-JS fallback -func routePanelWordFiltersEdit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { - header, stats, ferr := common.PanelUserCheck(w, r, &user) - if ferr != nil { - return ferr - } - if !user.Perms.EditSettings { - return common.NoPermissions(w, r, user) - } - header.Title = common.GetTitlePhrase("panel_edit_word_filter") - _ = wfid - - pi := common.PanelPage{&common.BasePanelPage{header, stats, "word-filters", common.ReportForumID}, tList, nil} - return panelRenderTemplate("panel_word_filters_edit", w, r, user, &pi) -} - -func routePanelWordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { - _, ferr := common.SimplePanelUserCheck(w, r, &user) - if ferr != nil { - return ferr - } - // TODO: Either call it isJs or js rather than flip-flopping back and forth across the routes x.x - isJs := (r.PostFormValue("isJs") == "1") - if !user.Perms.EditSettings { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - id, err := strconv.Atoi(wfid) - if err != nil { - return common.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, isJs) - } - - find := strings.TrimSpace(r.PostFormValue("find")) - if find == "" { - return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) - } - - // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement - replacement := strings.TrimSpace(r.PostFormValue("replacement")) - - _, err = stmts.updateWordFilter.Exec(find, replacement, id) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - wordFilters := common.WordFilterBox.Load().(common.WordFilterMap) - wordFilters[id] = common.WordFilter{ID: id, Find: find, Replacement: replacement} - common.WordFilterBox.Store(wordFilters) - - http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) - return nil -} - -func routePanelWordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { - _, ferr := common.SimplePanelUserCheck(w, r, &user) - if ferr != nil { - return ferr - } - - isJs := (r.PostFormValue("isJs") == "1") - if !user.Perms.EditSettings { - return common.NoPermissionsJSQ(w, r, user, isJs) - } - - id, err := strconv.Atoi(wfid) - if err != nil { - return common.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, isJs) - } - - _, err = stmts.deleteWordFilter.Exec(id) - if err != nil { - return common.InternalErrorJSQ(err, w, r, isJs) - } - - wordFilters := common.WordFilterBox.Load().(common.WordFilterMap) - delete(wordFilters, id) - common.WordFilterBox.Store(wordFilters) - - http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) - return nil -} - func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { header, stats, ferr := common.PanelUserCheck(w, r, &user) if ferr != nil { diff --git a/patcher/utils.go b/patcher/utils.go index c34aca1b..fcc7593a 100644 --- a/patcher/utils.go +++ b/patcher/utils.go @@ -32,8 +32,7 @@ func execStmt(stmt *sql.Stmt, err error) error { }*/ func eachUser(handle func(int) error) error { - acc := qgen.Builder.Accumulator() - err := acc.Select("users").Each(func(rows *sql.Rows) error { + err := qgen.NewAcc().Select("users").Each(func(rows *sql.Rows) error { var uid int err := rows.Scan(&uid) if err != nil { diff --git a/plugin_adventure.go b/plugin_adventure.go index 3f07dbe0..cdb61ffc 100644 --- a/plugin_adventure.go +++ b/plugin_adventure.go @@ -4,7 +4,16 @@ package main import "./common" func init() { - common.Plugins["adventure"] = common.NewPlugin("adventure", "WIP", "Azareal", "http://github.com/Azareal", "", "", "", initAdventure, nil, deactivateAdventure, installAdventure, nil) + common.Plugins.Add(&common.Plugin{ + UName: "adventure", + Name: "Adventure", + Tag: "WIP", + Author: "Azareal", + URL: "https://github.com/Azareal", + Init: initAdventure, + Deactivate: deactivateAdventure, + Install: installAdventure, + }) } func initAdventure() error { diff --git a/plugin_bbcode.go b/plugin_bbcode.go index f00b990e..46ad08ee 100644 --- a/plugin_bbcode.go +++ b/plugin_bbcode.go @@ -25,7 +25,7 @@ var bbcodeQuotes *regexp.Regexp var bbcodeCode *regexp.Regexp func init() { - common.Plugins["bbcode"] = common.NewPlugin("bbcode", "BBCode", "Azareal", "http://github.com/Azareal", "", "", "", initBbcode, nil, deactivateBbcode, nil, nil) + common.Plugins.Add(&common.Plugin{UName: "bbcode", Name: "BBCode", Author: "Azareal", URL: "https://github.com/Azareal", Init: initBbcode, Deactivate: deactivateBbcode}) } func initBbcode() error { diff --git a/plugin_guilds.go b/plugin_guilds.go index 31840b26..d548a911 100644 --- a/plugin_guilds.go +++ b/plugin_guilds.go @@ -1,8 +1,6 @@ package main import ( - //"fmt" - "./common" "./extend/guilds/lib" "./query_gen/lib" @@ -12,7 +10,7 @@ import ( // TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin? func init() { - common.Plugins["guilds"] = common.NewPlugin("guilds", "Guilds", "Azareal", "http://github.com/Azareal", "", "", "", initGuilds, nil, deactivateGuilds, installGuilds, nil) + common.Plugins.Add(&common.Plugin{UName: "guilds", Name: "Guilds", Author: "Azareal", URL: "https://github.com/Azareal", Init: initGuilds, Deactivate: deactivateGuilds, Install: installGuilds}) // TODO: Is it possible to avoid doing this when the plugin isn't activated? common.PrebuildTmplList = append(common.PrebuildTmplList, guilds.PrebuildTmplList) @@ -38,7 +36,7 @@ func initGuilds() (err error) { return err } - acc := qgen.Builder.Accumulator() + acc := qgen.NewAcc() guilds.ListStmt = acc.Select("guilds").Columns("guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime").Prepare() diff --git a/plugin_heythere.go b/plugin_heythere.go index 393c5e5e..285f23b5 100644 --- a/plugin_heythere.go +++ b/plugin_heythere.go @@ -3,7 +3,7 @@ package main import "./common" func init() { - common.Plugins["heythere"] = common.NewPlugin("heythere", "Hey There", "Azareal", "http://github.com/Azareal", "", "", "", initHeythere, nil, deactivateHeythere, nil, nil) + common.Plugins.Add(&common.Plugin{UName: "heythere", Name: "Hey There", Author: "Azareal", URL: "https://github.com/Azareal", Init: initHeythere, Deactivate: deactivateHeythere}) } // init_heythere is separate from init() as we don't want the plugin to run if the plugin is disabled diff --git a/plugin_markdown.go b/plugin_markdown.go index 1fbb5bc4..87dc1248 100644 --- a/plugin_markdown.go +++ b/plugin_markdown.go @@ -20,7 +20,7 @@ var markdownStrikeTagOpen []byte var markdownStrikeTagClose []byte func init() { - common.Plugins["markdown"] = common.NewPlugin("markdown", "Markdown", "Azareal", "http://github.com/Azareal", "", "", "", initMarkdown, nil, deactivateMarkdown, nil, nil) + common.Plugins.Add(&common.Plugin{UName: "markdown", Name: "Markdown", Author: "Azareal", URL: "https://github.com/Azareal", Init: initMarkdown, Deactivate: deactivateMarkdown}) } func initMarkdown() error { diff --git a/plugin_skeleton.go b/plugin_skeleton.go index 0ea954c9..2621d193 100644 --- a/plugin_skeleton.go +++ b/plugin_skeleton.go @@ -28,7 +28,7 @@ func init() { That Uninstallation field which is currently unused is for not only deactivating this plugin, but for purging any data associated with it such a new tables or data produced by the end-user. */ - common.Plugins["skeleton"] = common.NewPlugin("skeleton", "Skeleton", "Azareal", "", "", "", "", initSkeleton, activateSkeleton, deactivateSkeleton, nil, nil) + common.Plugins.Add(&common.Plugin{UName: "skeleton", Name: "Skeleton", Author: "Azareal", Init: initSkeleton, Activate: activateSkeleton, Deactivate: deactivateSkeleton}) } func initSkeleton() error { return nil } diff --git a/query_gen/lib/accumulator.go b/query_gen/lib/accumulator.go index e55b40c4..7752f9bd 100644 --- a/query_gen/lib/accumulator.go +++ b/query_gen/lib/accumulator.go @@ -8,6 +8,11 @@ import ( var LogPrepares = true +// So we don't have to do the qgen.Builder.Accumulator() boilerplate all the time +func NewAcc() *Accumulator { + return Builder.Accumulator() +} + type Accumulator struct { conn *sql.DB adapter Adapter @@ -35,7 +40,7 @@ func (build *Accumulator) FirstError() error { return build.firstErr } -func (build *Accumulator) recordError(err error) { +func (build *Accumulator) RecordError(err error) { if err == nil { return } @@ -50,11 +55,11 @@ func (build *Accumulator) prepare(res string, err error) *sql.Stmt { log.Print("res: ", res) } if err != nil { - build.recordError(err) + build.RecordError(err) return nil } stmt, err := build.conn.Prepare(res) - build.recordError(err) + build.RecordError(err) return stmt } @@ -77,16 +82,16 @@ func (build *Accumulator) exec(query string, args ...interface{}) (res sql.Resul func (build *Accumulator) Tx(handler func(*TransactionBuilder) error) { tx, err := build.conn.Begin() if err != nil { - build.recordError(err) + build.RecordError(err) return } err = handler(&TransactionBuilder{tx, build.adapter, nil}) if err != nil { tx.Rollback() - build.recordError(err) + build.RecordError(err) return } - build.recordError(tx.Commit()) + build.RecordError(tx.Commit()) } func (build *Accumulator) SimpleSelect(table string, columns string, where string, orderby string, limit string) *sql.Stmt { @@ -140,11 +145,11 @@ func (build *Accumulator) Purge(table string) *sql.Stmt { func (build *Accumulator) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) { if err != nil { - build.recordError(err) + build.RecordError(err) return nil } stmt, err = tx.Prepare(res) - build.recordError(err) + build.RecordError(err) return stmt } diff --git a/query_gen/main.go b/query_gen/main.go index 7bcf892f..e28f0619 100644 --- a/query_gen/main.go +++ b/query_gen/main.go @@ -284,8 +284,6 @@ func writeInserts(adapter qgen.Adapter) error { build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse() - build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse() - return nil } @@ -302,8 +300,6 @@ func writeUpdates(adapter qgen.Adapter) error { build.Update("setTempGroup").Table("users").Set("temp_group = ?").Where("uid = ?").Parse() - build.Update("updateWordFilter").Table("word_filters").Set("find = ?, replacement = ?").Where("wfid = ?").Parse() - build.Update("bumpSync").Table("sync").Set("last_update = UTC_TIMESTAMP()").Parse() return nil @@ -317,8 +313,6 @@ func writeDeletes(adapter qgen.Adapter) error { build.Delete("deleteActivityStreamMatch").Table("activity_stream_matches").Where("watcher = ? AND asid = ?").Parse() //build.Delete("deleteActivityStreamMatchesByWatcher").Table("activity_stream_matches").Where("watcher = ?").Parse() - build.Delete("deleteWordFilter").Table("word_filters").Where("wfid = ?").Parse() - return nil } diff --git a/router_gen/routes.go b/router_gen/routes.go index ade75fbf..0f59c2a1 100644 --- a/router_gen/routes.go +++ b/router_gen/routes.go @@ -155,11 +155,11 @@ func buildPanelRoutes() { View("panel.SettingEdit", "/panel/settings/edit/", "extraData"), Action("panel.SettingEditSubmit", "/panel/settings/edit/submit/", "extraData"), - View("routePanelWordFilters", "/panel/settings/word-filters/"), - Action("routePanelWordFiltersCreateSubmit", "/panel/settings/word-filters/create/"), - View("routePanelWordFiltersEdit", "/panel/settings/word-filters/edit/", "extraData"), - Action("routePanelWordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extraData"), - Action("routePanelWordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extraData"), + View("panel.WordFilters", "/panel/settings/word-filters/"), + Action("panel.WordFiltersCreateSubmit", "/panel/settings/word-filters/create/"), + View("panel.WordFiltersEdit", "/panel/settings/word-filters/edit/", "extraData"), + Action("panel.WordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extraData"), + Action("panel.WordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extraData"), View("panel.Pages", "/panel/pages/").Before("AdminOnly"), Action("panel.PagesCreateSubmit", "/panel/pages/create/submit/").Before("AdminOnly"), diff --git a/routes/account.go b/routes/account.go index 966ec79d..4ca85fac 100644 --- a/routes/account.go +++ b/routes/account.go @@ -319,8 +319,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user common.U } // TODO: Add an EmailStore and move this there - acc := qgen.Builder.Accumulator() - _, err = acc.Insert("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Exec(email, uid, 0, token) + _, err = qgen.NewAcc().Insert("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Exec(email, uid, 0, token) if err != nil { return common.InternalError(err, w, r) } @@ -416,8 +415,7 @@ func AccountEditPasswordSubmit(w http.ResponseWriter, r *http.Request, user comm confirmPassword := r.PostFormValue("account-confirm-password") // TODO: Use a reusable statement - acc := qgen.Builder.Accumulator() - err := acc.Select("users").Columns("password, salt").Where("uid = ?").QueryRow(user.ID).Scan(&realPassword, &salt) + err := qgen.NewAcc().Select("users").Columns("password, salt").Where("uid = ?").QueryRow(user.ID).Scan(&realPassword, &salt) if err == sql.ErrNoRows { return common.LocalError("Your account no longer exists.", w, r, user) } else if err != nil { diff --git a/routes/panel/analytics.go b/routes/panel/analytics.go index b66e1e89..a9aa70a3 100644 --- a/routes/panel/analytics.go +++ b/routes/panel/analytics.go @@ -133,8 +133,7 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) co common.DebugLog("in panel.AnalyticsViews") // TODO: Add some sort of analytics store / iterator? - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -170,9 +169,8 @@ func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.Use revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) common.DebugLog("in panel.AnalyticsRouteViews") - acc := qgen.Builder.Accumulator() // TODO: Validate the route is valid - rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route) + rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -211,9 +209,8 @@ func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.Use agent = common.SanitiseSingleLine(agent) common.DebugLog("in panel.AnalyticsAgentViews") - acc := qgen.Builder.Accumulator() // TODO: Verify the agent is valid - rows, err := acc.Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent) + rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -256,9 +253,8 @@ func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.Use } common.DebugLog("in panel.AnalyticsForumViews") - acc := qgen.Builder.Accumulator() // TODO: Verify the agent is valid - rows, err := acc.Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid) + rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -298,9 +294,8 @@ func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.Us system = common.SanitiseSingleLine(system) common.DebugLog("in panel.AnalyticsSystemViews") - acc := qgen.Builder.Accumulator() // TODO: Verify the OS name is valid - rows, err := acc.Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system) + rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -339,9 +334,8 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common. lang = common.SanitiseSingleLine(lang) common.DebugLog("in panel.AnalyticsLanguageViews") - acc := qgen.Builder.Accumulator() // TODO: Verify the language code is valid - rows, err := acc.Select("viewchunks_langs").Columns("count, createdAt").Where("lang = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang) + rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, createdAt").Where("lang = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -379,9 +373,8 @@ func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common. revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) common.DebugLog("in panel.AnalyticsReferrerViews") - acc := qgen.Builder.Accumulator() // TODO: Verify the agent is valid - rows, err := acc.Select("viewchunks_referrers").Columns("count, createdAt").Where("domain = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain) + rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, createdAt").Where("domain = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -414,8 +407,7 @@ func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) c revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) common.DebugLog("in panel.AnalyticsTopics") - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -450,8 +442,7 @@ func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) co revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) common.DebugLog("in panel.AnalyticsPosts") - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -505,8 +496,7 @@ func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) c return common.LocalError(err.Error(), w, r, user) } - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks_forums").Columns("count, forum").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, forum").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -548,8 +538,7 @@ func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) c return common.LocalError(err.Error(), w, r, user) } - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -582,8 +571,7 @@ func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) c return common.LocalError(err.Error(), w, r, user) } - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -621,8 +609,7 @@ func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) return common.LocalError(err.Error(), w, r, user) } - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks_systems").Columns("count, system").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, system").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -660,8 +647,7 @@ func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User return common.LocalError(err.Error(), w, r, user) } - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } @@ -700,8 +686,7 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User return common.LocalError(err.Error(), w, r, user) } - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() + rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query() if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } diff --git a/routes/panel/word_filters.go b/routes/panel/word_filters.go new file mode 100644 index 00000000..69ca8b72 --- /dev/null +++ b/routes/panel/word_filters.go @@ -0,0 +1,133 @@ +package panel + +import ( + "database/sql" + "net/http" + "strconv" + "strings" + + "../../common" +) + +//routePanelWordFilter +func WordFilters(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + basePage, ferr := buildBasePage(w, r, &user, "word_filters", "word-filters") + if ferr != nil { + return ferr + } + if !user.Perms.EditSettings { + return common.NoPermissions(w, r, user) + } + + filterList, err := common.WordFilters.GetAll() + if err != nil { + return common.InternalError(err, w, r) + } + + pi := common.PanelPage{basePage, tList, filterList} + return panelRenderTemplate("panel_word_filters", w, r, user, &pi) +} + +//routePanelWordFiltersCreateSubmit +func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + if !user.Perms.EditSettings { + return common.NoPermissions(w, r, user) + } + isJs := (r.PostFormValue("js") == "1") + + // ? - We're not doing a full sanitise here, as it would be useful if admins were able to put down rules for replacing things with HTML, etc. + find := strings.TrimSpace(r.PostFormValue("find")) + if find == "" { + return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) + } + + // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement + replacement := strings.TrimSpace(r.PostFormValue("replacement")) + + err := common.WordFilters.Create(find, replacement) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + return panelSuccessRedirect("/panel/settings/word-filters/", w, r, isJs) +} + +// TODO: Implement this as a non-JS fallback +//routePanelWordFiltersEdit +func WordFiltersEdit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { + basePage, ferr := buildBasePage(w, r, &user, "edit_word_filter", "word-filters") + if ferr != nil { + return ferr + } + if !user.Perms.EditSettings { + return common.NoPermissions(w, r, user) + } + _ = wfid + + pi := common.PanelPage{basePage, tList, nil} + return panelRenderTemplate("panel_word_filters_edit", w, r, user, &pi) +} + +//routePanelWordFiltersEditSubmit +func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + // TODO: Either call it isJs or js rather than flip-flopping back and forth across the routes x.x + isJs := (r.PostFormValue("isJs") == "1") + if !user.Perms.EditSettings { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + id, err := strconv.Atoi(wfid) + if err != nil { + return common.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, isJs) + } + + find := strings.TrimSpace(r.PostFormValue("find")) + if find == "" { + return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) + } + + // Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement + replacement := strings.TrimSpace(r.PostFormValue("replacement")) + + err = common.WordFilters.Update(id, find, replacement) + if err != nil { + return common.InternalErrorJSQ(err, w, r, isJs) + } + + http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) + return nil +} + +//routePanelWordFiltersDeleteSubmit +func WordFiltersDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError { + _, ferr := common.SimplePanelUserCheck(w, r, &user) + if ferr != nil { + return ferr + } + + isJs := (r.PostFormValue("isJs") == "1") + if !user.Perms.EditSettings { + return common.NoPermissionsJSQ(w, r, user, isJs) + } + + id, err := strconv.Atoi(wfid) + if err != nil { + return common.LocalErrorJSQ("The word filter ID must be an integer.", w, r, user, isJs) + } + + err = common.WordFilters.Delete(id) + if err == sql.ErrNoRows { + return common.LocalErrorJSQ("This word filter doesn't exist", w, r, user, isJs) + } + + http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther) + return nil +} diff --git a/routes/poll.go b/routes/poll.go index 36ae7ca5..a37e5714 100644 --- a/routes/poll.go +++ b/routes/poll.go @@ -83,8 +83,7 @@ func PollResults(w http.ResponseWriter, r *http.Request, user common.User, sPoll } // TODO: Abstract this - acc := qgen.Builder.Accumulator() - rows, err := acc.Select("polls_options").Columns("votes").Where("pollID = ?").Orderby("option ASC").Query(poll.ID) + rows, err := qgen.NewAcc().Select("polls_options").Columns("votes").Where("pollID = ?").Orderby("option ASC").Query(poll.ID) if err != nil { return common.InternalError(err, w, r) } diff --git a/routes/topic.go b/routes/topic.go index 073232ca..3716737c 100644 --- a/routes/topic.go +++ b/routes/topic.go @@ -194,7 +194,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit // TODO: Add a config setting to disable the liked query for a burst of extra speed if user.Liked > 0 && len(likedQueryList) > 1 /*&& user.LastLiked <= time.Now()*/ { - rows, err := qgen.Builder.Accumulator().Select("likes").Columns("targetItem").Where("sentBy = ? AND targetType = 'replies'").In("targetItem", likedQueryList[1:]).Query(user.ID) + rows, err := qgen.NewAcc().Select("likes").Columns("targetItem").Where("sentBy = ? AND targetType = 'replies'").In("targetItem", likedQueryList[1:]).Query(user.ID) if err != nil && err != sql.ErrNoRows { return common.InternalError(err, w, r) } diff --git a/templates/panel_word_filters.html b/templates/panel_word_filters.html index 3d5ed075..139c3672 100644 --- a/templates/panel_word_filters.html +++ b/templates/panel_word_filters.html @@ -9,12 +9,12 @@
{{range .Something}} diff --git a/themes/nox/public/main.css b/themes/nox/public/main.css index 07ef414d..e5085e68 100644 --- a/themes/nox/public/main.css +++ b/themes/nox/public/main.css @@ -414,7 +414,7 @@ h2 { display: block; } -input, select, button, .formbutton, textarea { +input, select, button, .formbutton, .panel_right_button, textarea { border-radius: 3px; background: rgb(90,90,90); color: rgb(200,200,200); @@ -434,7 +434,7 @@ input { padding-bottom: 3px; font-size: 16px; } -button, .formbutton { +button, .formbutton, .panel_right_button { background: rgb(110,110,210); color: rgb(250,250,250); font-family: "Segoe UI"; diff --git a/themes/nox/public/panel.css b/themes/nox/public/panel.css index 5ea446c8..d76ae280 100644 --- a/themes/nox/public/panel.css +++ b/themes/nox/public/panel.css @@ -83,7 +83,7 @@ padding: 12px; } -.to_right { +.to_right, .panel_buttons { margin-left: auto; } @@ -118,9 +118,18 @@ button, .formbutton { /*background: rgb(110,110,210); color: rgb(250,250,250);*/ } -button, .formbutton { +button, .formbutton, .panel_right_button { background: rgb(100,100,200); } +.panel_right_button { + margin-left: 2px; +} +.edit_button:after { + content: "{{index .Phrases "panel_edit_button_text"}}"; +} +.delete_button:after { + content: "{{index .Phrases "panel_delete_button_text"}}"; +} #themeSelector select { background: rgb(90,90,90); color: rgb(200,200,200);