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}}
- {{.Find}} + {{.Find}} - {{.Replacement}} + {{.Replacement}} - +
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);