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.
This commit is contained in:
Azareal 2018-08-04 21:46:36 +10:00
parent 8e81f922ea
commit 01a692ab5b
57 changed files with 414 additions and 390 deletions

View File

@ -20,7 +20,6 @@ before_script:
- ./cc-test-reporter before-build - ./cc-test-reporter before-build
script: ./run-linux-tests script: ./run-linux-tests
after_script: after_script:
- ls
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
addons: addons:
mariadb: '10.0' mariadb: '10.0'

View File

@ -17,7 +17,7 @@ type DefaultAttachmentStore struct {
} }
func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) { func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &DefaultAttachmentStore{ return &DefaultAttachmentStore{
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(), add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
}, acc.FirstError() }, acc.FirstError()

View File

@ -85,7 +85,7 @@ type DefaultAuth struct {
// NewDefaultAuth is a factory for spitting out DefaultAuths // NewDefaultAuth is a factory for spitting out DefaultAuths
func NewDefaultAuth() (*DefaultAuth, error) { func NewDefaultAuth() (*DefaultAuth, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &DefaultAuth{ return &DefaultAuth{
login: acc.Select("users").Columns("uid, password, salt").Where("name = ?").Prepare(), login: acc.Select("users").Columns("uid, password, salt").Where("name = ?").Prepare(),
logout: acc.Update("users").Set("session = ''").Where("uid = ?").Prepare(), logout: acc.Update("users").Set("session = ''").Where("uid = ?").Prepare(),

View File

@ -83,8 +83,7 @@ var DbInits dbInits
func (inits dbInits) Run() error { func (inits dbInits) Run() error {
for _, init := range inits { for _, init := range inits {
acc := qgen.Builder.Accumulator() err := init(qgen.NewAcc())
err := init(acc)
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,7 +15,7 @@ type DefaultAgentViewCounter struct {
} }
func NewDefaultAgentViewCounter() (*DefaultAgentViewCounter, error) { func NewDefaultAgentViewCounter() (*DefaultAgentViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
var agentBuckets = make([]*RWMutexCounterBucket, len(agentMapEnum)) var agentBuckets = make([]*RWMutexCounterBucket, len(agentMapEnum))
for bucketID, _ := range agentBuckets { for bucketID, _ := range agentBuckets {
agentBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} agentBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}

View File

@ -22,7 +22,7 @@ type DefaultForumViewCounter struct {
} }
func NewDefaultForumViewCounter() (*DefaultForumViewCounter, error) { func NewDefaultForumViewCounter() (*DefaultForumViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
counter := &DefaultForumViewCounter{ counter := &DefaultForumViewCounter{
oddMap: make(map[int]*RWMutexCounterBucket), oddMap: make(map[int]*RWMutexCounterBucket),
evenMap: make(map[int]*RWMutexCounterBucket), evenMap: make(map[int]*RWMutexCounterBucket),

View File

@ -100,7 +100,7 @@ type DefaultLangViewCounter struct {
} }
func NewDefaultLangViewCounter() (*DefaultLangViewCounter, error) { func NewDefaultLangViewCounter() (*DefaultLangViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
var langBuckets = make([]*RWMutexCounterBucket, len(langCodes)) var langBuckets = make([]*RWMutexCounterBucket, len(langCodes))
for bucketID, _ := range langBuckets { for bucketID, _ := range langBuckets {

View File

@ -18,7 +18,7 @@ type DefaultPostCounter struct {
} }
func NewPostCounter() (*DefaultPostCounter, error) { func NewPostCounter() (*DefaultPostCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
counter := &DefaultPostCounter{ counter := &DefaultPostCounter{
currentBucket: 0, currentBucket: 0,
insert: acc.Insert("postchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(), insert: acc.Insert("postchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),

View File

@ -30,7 +30,7 @@ type DefaultReferrerTracker struct {
} }
func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) { func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
refTracker := &DefaultReferrerTracker{ refTracker := &DefaultReferrerTracker{
odd: make(map[string]*ReferrerItem), odd: make(map[string]*ReferrerItem),
even: make(map[string]*ReferrerItem), even: make(map[string]*ReferrerItem),

View File

@ -13,7 +13,7 @@ type DefaultRouteViewCounter struct {
} }
func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) { func NewDefaultRouteViewCounter() (*DefaultRouteViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum)) var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum))
for bucketID, _ := range routeBuckets { for bucketID, _ := range routeBuckets {
routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}

View File

@ -12,7 +12,7 @@ type DefaultOSViewCounter struct {
} }
func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) { func NewDefaultOSViewCounter() (*DefaultOSViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
var osBuckets = make([]*RWMutexCounterBucket, len(osMapEnum)) var osBuckets = make([]*RWMutexCounterBucket, len(osMapEnum))
for bucketID, _ := range osBuckets { for bucketID, _ := range osBuckets {
osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0} osBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}

View File

@ -18,7 +18,7 @@ type DefaultTopicCounter struct {
} }
func NewTopicCounter() (*DefaultTopicCounter, error) { func NewTopicCounter() (*DefaultTopicCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
counter := &DefaultTopicCounter{ counter := &DefaultTopicCounter{
currentBucket: 0, currentBucket: 0,
insert: acc.Insert("topicchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(), insert: acc.Insert("topicchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),

View File

@ -21,7 +21,7 @@ type DefaultTopicViewCounter struct {
} }
func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) { func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
counter := &DefaultTopicViewCounter{ counter := &DefaultTopicViewCounter{
oddTopics: make(map[int]*RWMutexCounterBucket), oddTopics: make(map[int]*RWMutexCounterBucket),
evenTopics: make(map[int]*RWMutexCounterBucket), evenTopics: make(map[int]*RWMutexCounterBucket),

View File

@ -6,6 +6,7 @@
*/ */
package common package common
// TODO: Break this file up into multiple files to make it easier to maintain
import ( import (
"database/sql" "database/sql"
"errors" "errors"
@ -22,6 +23,22 @@ type PluginList map[string]*Plugin
// TODO: Have a proper store rather than a map? // TODO: Have a proper store rather than a map?
var Plugins PluginList = make(map[string]*Plugin) 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? // 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{}{ var Hooks = map[string][]func(interface{}) interface{}{
"forums_frow_assign": nil, "forums_frow_assign": nil,
@ -158,7 +175,7 @@ type Plugin struct {
Activate func() error Activate func() error
Deactivate func() // TODO: We might want to let this return an error? Deactivate func() // TODO: We might want to let this return an error?
Install func() error Install func() error
Uninstall func() error Uninstall func() error // TODO: I'm not sure uninstall is implemented
Hooks map[string]int Hooks map[string]int
Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins 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() 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? // ? - Is this racey?
// TODO: Generate the cases in this switch // TODO: Generate the cases in this switch
func (plugin *Plugin) AddHook(name string, handler interface{}) { func (plugin *Plugin) AddHook(name string, handler interface{}) {

View File

@ -33,7 +33,7 @@ type MemoryForumPermsStore struct {
} }
func NewMemoryForumPermsStore() (*MemoryForumPermsStore, error) { func NewMemoryForumPermsStore() (*MemoryForumPermsStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &MemoryForumPermsStore{ return &MemoryForumPermsStore{
getByForum: acc.Select("forums_permissions").Columns("gid, permissions").Where("fid = ?").Orderby("gid ASC").Prepare(), 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(), getByForumGroup: acc.Select("forums_permissions").Columns("permissions").Where("fid = ? AND gid = ?").Prepare(),

View File

@ -70,7 +70,7 @@ type MemoryForumStore struct {
// NewMemoryForumStore gives you a new instance of MemoryForumStore // NewMemoryForumStore gives you a new instance of MemoryForumStore
func NewMemoryForumStore() (*MemoryForumStore, error) { func NewMemoryForumStore() (*MemoryForumStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
// TODO: Do a proper delete // TODO: Do a proper delete
return &MemoryForumStore{ return &MemoryForumStore{
get: acc.Select("forums").Columns("name, desc, active, preset, parentID, parentType, topicCount, lastTopicID, lastReplyerID").Where("fid = ?").Prepare(), 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 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 // GlobalCount returns the total number of forums
func (mfs *MemoryForumStore) GlobalCount() (fcount int) { func (mfs *MemoryForumStore) GlobalCount() (fcount int) {
err := mfs.count.QueryRow().Scan(&fcount) err := mfs.count.QueryRow().Scan(&fcount)

View File

@ -45,7 +45,7 @@ type MemoryGroupStore struct {
} }
func NewMemoryGroupStore() (*MemoryGroupStore, error) { func NewMemoryGroupStore() (*MemoryGroupStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &MemoryGroupStore{ return &MemoryGroupStore{
groups: make(map[int]*Group), groups: make(map[int]*Group),
groupCount: 0, groupCount: 0,

View File

@ -21,7 +21,7 @@ type DefaultIPSearcher struct {
// NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher // NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher
func NewDefaultIPSearcher() (*DefaultIPSearcher, error) { func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &DefaultIPSearcher{ return &DefaultIPSearcher{
searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(), 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(), searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(),

View File

@ -40,8 +40,7 @@ func (store *DefaultMenuStore) Get(mid int) (*MenuListHolder, error) {
} }
func (store *DefaultMenuStore) Items(mid int) (mlist MenuItemList, err error) { func (store *DefaultMenuStore) Items(mid int) (mlist MenuItemList, err error) {
acc := qgen.Builder.Accumulator() 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 {
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 {
var mitem = MenuItem{MenuID: mid} 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) 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 { if err != nil {

View File

@ -61,12 +61,20 @@ func (js *OttoPluginLang) AddPlugin(meta PluginMeta) (plugin *Plugin, err error)
} }
return nil 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 plugin.Data = script
return plugin, nil return plugin, nil

View File

@ -413,7 +413,11 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
//msg = url_reg.ReplaceAllString(msg,"<a href=\"$2$3//$4\" rel=\"nofollow\">$2$3//$4</a>") //msg = url_reg.ReplaceAllString(msg,"<a href=\"$2$3//$4\" rel=\"nofollow\">$2$3//$4</a>")
// Word filter list. E.g. Swear words and other things the admins don't like // 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 { for _, filter := range wordFilters {
msg = strings.Replace(msg, filter.Find, filter.Replacement, -1) msg = strings.Replace(msg, filter.Find, filter.Replacement, -1)
} }

View File

@ -72,7 +72,7 @@ type DefaultPollStore struct {
} }
func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) { func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
if cache == nil { if cache == nil {
cache = NewNullPollCache() cache = NewNullPollCache()
} }
@ -153,8 +153,7 @@ func (store *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err er
} }
qlist = qlist[0 : len(qlist)-1] qlist = qlist[0 : len(qlist)-1]
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("polls").Columns("pollID, parentID, parentTable, type, options, votes").Where("pollID IN(" + qlist + ")").Query(pollIDList...)
rows, err := acc.Select("polls").Columns("pollID, parentID, parentTable, type, options, votes").Where("pollID IN(" + qlist + ")").Query(pollIDList...)
if err != nil { if err != nil {
return list, err return list, err
} }

View File

@ -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 // 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.Users = Users.GlobalCount()
stats.Groups = Groups.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.Pages = Pages.GlobalCount()
stats.Settings = len(header.Settings) stats.Settings = len(header.Settings)
stats.WordFilters = len(WordFilterBox.Load().(WordFilterMap)) stats.WordFilters = WordFilters.EstCount()
stats.Themes = len(Themes) stats.Themes = len(Themes)
stats.Reports = 0 // TODO: Do the report count. Only show open threads? stats.Reports = 0 // TODO: Do the report count. Only show open threads?

View File

@ -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) { func (store *DefaultStatStore) countTable(table string, duration int, unit string) (stat int, err error) {
/*acc := qgen.Builder.Accumulator() /*counter := qgen.NewAcc().Count("replies").DateCutoff("createdAt", 1, "day").Prepare()
counter := acc.Count("replies").DateCutoff("createdAt", 1, "day").Prepare()
if acc.FirstError() != nil { if acc.FirstError() != nil {
return 0, acc.FirstError() return 0, acc.FirstError()
} }

View File

@ -15,7 +15,7 @@ type DefaultSubscriptionStore struct {
} }
func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) { func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &DefaultSubscriptionStore{ return &DefaultSubscriptionStore{
add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(), add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(),
}, acc.FirstError() }, acc.FirstError()

View File

@ -93,6 +93,7 @@ func HandleExpiredScheduledGroups() error {
// TODO: Use AddScheduledSecondTask // TODO: Use AddScheduledSecondTask
// TODO: Be a little more granular with the synchronisation // TODO: Be a little more granular with the synchronisation
// TODO: Synchronise more things // TODO: Synchronise more things
// TODO: Does this even work?
func HandleServerSync() error { func HandleServerSync() error {
// We don't want to run any unnecessary queries when there is nothing to synchronise // We don't want to run any unnecessary queries when there is nothing to synchronise
/*if Config.ServerCount > 1 { /*if Config.ServerCount > 1 {
@ -118,7 +119,7 @@ func HandleServerSync() error {
log.Print("Unable to reload the settings") log.Print("Unable to reload the settings")
return err return err
} }
err = LoadWordFilters() err = WordFilters.ReloadAll()
if err != nil { if err != nil {
log.Print("Unable to reload the word filters") log.Print("Unable to reload the word filters")
return err return err

View File

@ -13,20 +13,18 @@ import (
) )
func ThumbTask(thumbChan chan bool) { func ThumbTask(thumbChan chan bool) {
acc := qgen.Builder.Accumulator()
for { for {
// Put this goroutine to sleep until we have work to do // Put this goroutine to sleep until we have work to do
<-thumbChan <-thumbChan
// TODO: Use a real queue // TODO: Use a real queue
acc := qgen.NewAcc()
err := acc.Select("users_avatar_queue").Columns("uid").Limit("0,5").EachInt(func(uid int) error { 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? // TODO: Do a bulk user fetch instead?
user, err := Users.Get(uid) user, err := Users.Get(uid)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
//log.Print("user.RawAvatar: ", user.RawAvatar)
// Has the avatar been removed or already been processed by the thumbnailer? // Has the avatar been removed or already been processed by the thumbnailer?
if len(user.RawAvatar) < 2 || user.RawAvatar[1] == '.' { if len(user.RawAvatar) < 2 || user.RawAvatar[1] == '.' {
@ -59,6 +57,9 @@ func ThumbTask(thumbChan chan bool) {
if err != nil { if err != nil {
LogError(err) LogError(err)
} }
if err = acc.FirstError(); err != nil {
LogError(err)
}
} }
} }

View File

@ -51,7 +51,7 @@ type DefaultTopicStore struct {
// NewDefaultTopicStore gives you a new instance of DefaultTopicStore // NewDefaultTopicStore gives you a new instance of DefaultTopicStore
func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) { func NewDefaultTopicStore(cache TopicCache) (*DefaultTopicStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
if cache == nil { if cache == nil {
cache = NewNullTopicCache() cache = NewNullTopicCache()
} }

View File

@ -45,7 +45,7 @@ type DefaultUserStore struct {
// NewDefaultUserStore gives you a new instance of DefaultUserStore // NewDefaultUserStore gives you a new instance of DefaultUserStore
func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) { func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
if cache == nil { if cache == nil {
cache = NewNullUserCache() cache = NewNullUserCache()
} }
@ -154,8 +154,7 @@ func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err erro
} }
qlist = qlist[0 : len(qlist)-1] qlist = qlist[0 : len(qlist)-1]
acc := qgen.Builder.Accumulator() 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...)
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...)
if err != nil { if err != nil {
return list, err return list, err
} }

View File

@ -7,34 +7,55 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
// TODO: Move some features into methods on this?
type WordFilter struct { type WordFilter struct {
ID int ID int
Find string Find string
Replacement string Replacement string
} }
type WordFilterMap map[int]WordFilter
var WordFilterBox atomic.Value // An atomic value holding a WordFilterBox var WordFilters WordFilterStore
type FilterStmts struct { type WordFilterStore interface {
getWordFilters *sql.Stmt 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() { getAll *sql.Stmt
WordFilterBox.Store(WordFilterMap(make(map[int]WordFilter))) create *sql.Stmt
DbInits.Add(func(acc *qgen.Accumulator) error { delete *sql.Stmt
filterStmts = FilterStmts{ update *sql.Stmt
getWordFilters: acc.Select("word_filters").Columns("wfid, find, replacement").Prepare(), count *sql.Stmt
}
return acc.FirstError()
})
} }
func LoadWordFilters() error { func NewDefaultWordFilterStore(acc *qgen.Accumulator) (*DefaultWordFilterStore, error) {
var wordFilters = WordFilterMap(make(map[int]WordFilter)) store := &DefaultWordFilterStore{
filters, err := wordFilters.BypassGetAll() 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 { if err != nil {
return err return err
} }
@ -43,20 +64,20 @@ func LoadWordFilters() error {
wordFilters[filter.ID] = filter wordFilters[filter.ID] = filter
} }
WordFilterBox.Store(wordFilters) store.box.Store(wordFilters)
return nil return nil
} }
// TODO: Return pointers to word filters intead to save memory? // ? - Return pointers to word filters intead to save memory? -- A map is a pointer.
func (wBox WordFilterMap) BypassGetAll() (filters []WordFilter, err error) { func (store *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err error) {
rows, err := filterStmts.getWordFilters.Query() rows, err := store.getAll.Query()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
filter := WordFilter{ID: 0} filter := &WordFilter{ID: 0}
err := rows.Scan(&filter.ID, &filter.Find, &filter.Replacement) err := rows.Scan(&filter.ID, &filter.Find, &filter.Replacement)
if err != nil { if err != nil {
return filters, err return filters, err
@ -66,8 +87,52 @@ func (wBox WordFilterMap) BypassGetAll() (filters []WordFilter, err error) {
return filters, rows.Err() return filters, rows.Err()
} }
func AddWordFilter(id int, find string, replacement string) { // 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
wordFilters := WordFilterBox.Load().(WordFilterMap) func (store *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err error) {
wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement} return store.box.Load().(map[int]*WordFilter), nil
WordFilterBox.Store(wordFilters) }
// 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
} }

View File

@ -16,7 +16,7 @@ type SQLGuildStore struct {
} }
func NewSQLGuildStore() (*SQLGuildStore, error) { func NewSQLGuildStore() (*SQLGuildStore, error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
return &SQLGuildStore{ return &SQLGuildStore{
get: acc.Select("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Where("guildID = ?").Prepare(), 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(), create: acc.Insert("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Fields("?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),

View File

@ -15,16 +15,13 @@ type Stmts struct {
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
createWordFilter *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
updateGroupPerms *sql.Stmt updateGroupPerms *sql.Stmt
updateGroup *sql.Stmt updateGroup *sql.Stmt
updateEmail *sql.Stmt updateEmail *sql.Stmt
setTempGroup *sql.Stmt setTempGroup *sql.Stmt
updateWordFilter *sql.Stmt
bumpSync *sql.Stmt bumpSync *sql.Stmt
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
getActivityCountByWatcher *sql.Stmt getActivityCountByWatcher *sql.Stmt
@ -88,14 +85,6 @@ func _gen_mssql() (err error) {
return err 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.") common.DebugLog("Preparing updateTheme statement.")
stmts.updateTheme, err = db.Prepare("UPDATE [themes] SET [default] = ? WHERE [uname] = ?") stmts.updateTheme, err = db.Prepare("UPDATE [themes] SET [default] = ? WHERE [uname] = ?")
if err != nil { if err != nil {
@ -136,14 +125,6 @@ func _gen_mssql() (err error) {
return err 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.") common.DebugLog("Preparing bumpSync statement.")
stmts.bumpSync, err = db.Prepare("UPDATE [sync] SET [last_update] = GETUTCDATE()") stmts.bumpSync, err = db.Prepare("UPDATE [sync] SET [last_update] = GETUTCDATE()")
if err != nil { if err != nil {
@ -160,13 +141,5 @@ func _gen_mssql() (err error) {
return err 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 return nil
} }

View File

@ -17,16 +17,13 @@ type Stmts struct {
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
createWordFilter *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
updateGroupPerms *sql.Stmt updateGroupPerms *sql.Stmt
updateGroup *sql.Stmt updateGroup *sql.Stmt
updateEmail *sql.Stmt updateEmail *sql.Stmt
setTempGroup *sql.Stmt setTempGroup *sql.Stmt
updateWordFilter *sql.Stmt
bumpSync *sql.Stmt bumpSync *sql.Stmt
deleteActivityStreamMatch *sql.Stmt deleteActivityStreamMatch *sql.Stmt
deleteWordFilter *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
getActivityCountByWatcher *sql.Stmt getActivityCountByWatcher *sql.Stmt
@ -84,13 +81,6 @@ func _gen_mysql() (err error) {
return err 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.") common.DebugLog("Preparing updateTheme statement.")
stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?")
if err != nil { if err != nil {
@ -126,13 +116,6 @@ func _gen_mysql() (err error) {
return err 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.") common.DebugLog("Preparing bumpSync statement.")
stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = UTC_TIMESTAMP()") stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = UTC_TIMESTAMP()")
if err != nil { if err != nil {
@ -147,12 +130,5 @@ func _gen_mysql() (err error) {
return err 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 return nil
} }

View File

@ -11,13 +11,11 @@ import "./common"
type Stmts struct { type Stmts struct {
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
addTheme *sql.Stmt addTheme *sql.Stmt
createWordFilter *sql.Stmt
updateTheme *sql.Stmt updateTheme *sql.Stmt
updateGroupPerms *sql.Stmt updateGroupPerms *sql.Stmt
updateGroup *sql.Stmt updateGroup *sql.Stmt
updateEmail *sql.Stmt updateEmail *sql.Stmt
setTempGroup *sql.Stmt setTempGroup *sql.Stmt
updateWordFilter *sql.Stmt
bumpSync *sql.Stmt bumpSync *sql.Stmt
getActivityFeedByWatcher *sql.Stmt getActivityFeedByWatcher *sql.Stmt
@ -48,13 +46,6 @@ func _gen_pgsql() (err error) {
return err 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.") common.DebugLog("Preparing updateTheme statement.")
stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?") stmts.updateTheme, err = db.Prepare("UPDATE `themes` SET `default` = ? WHERE `uname` = ?")
if err != nil { if err != nil {
@ -90,13 +81,6 @@ func _gen_pgsql() (err error) {
return err 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.") common.DebugLog("Preparing bumpSync statement.")
stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = LOCALTIMESTAMP()") stmts.bumpSync, err = db.Prepare("UPDATE `sync` SET `last_update` = LOCALTIMESTAMP()")
if err != nil { if err != nil {

View File

@ -46,11 +46,11 @@ var RouteMap = map[string]interface{}{
"panel.Settings": panel.Settings, "panel.Settings": panel.Settings,
"panel.SettingEdit": panel.SettingEdit, "panel.SettingEdit": panel.SettingEdit,
"panel.SettingEditSubmit": panel.SettingEditSubmit, "panel.SettingEditSubmit": panel.SettingEditSubmit,
"routePanelWordFilters": routePanelWordFilters, "panel.WordFilters": panel.WordFilters,
"routePanelWordFiltersCreateSubmit": routePanelWordFiltersCreateSubmit, "panel.WordFiltersCreateSubmit": panel.WordFiltersCreateSubmit,
"routePanelWordFiltersEdit": routePanelWordFiltersEdit, "panel.WordFiltersEdit": panel.WordFiltersEdit,
"routePanelWordFiltersEditSubmit": routePanelWordFiltersEditSubmit, "panel.WordFiltersEditSubmit": panel.WordFiltersEditSubmit,
"routePanelWordFiltersDeleteSubmit": routePanelWordFiltersDeleteSubmit, "panel.WordFiltersDeleteSubmit": panel.WordFiltersDeleteSubmit,
"panel.Pages": panel.Pages, "panel.Pages": panel.Pages,
"panel.PagesCreateSubmit": panel.PagesCreateSubmit, "panel.PagesCreateSubmit": panel.PagesCreateSubmit,
"panel.PagesEdit": panel.PagesEdit, "panel.PagesEdit": panel.PagesEdit,
@ -175,11 +175,11 @@ var routeMapEnum = map[string]int{
"panel.Settings": 22, "panel.Settings": 22,
"panel.SettingEdit": 23, "panel.SettingEdit": 23,
"panel.SettingEditSubmit": 24, "panel.SettingEditSubmit": 24,
"routePanelWordFilters": 25, "panel.WordFilters": 25,
"routePanelWordFiltersCreateSubmit": 26, "panel.WordFiltersCreateSubmit": 26,
"routePanelWordFiltersEdit": 27, "panel.WordFiltersEdit": 27,
"routePanelWordFiltersEditSubmit": 28, "panel.WordFiltersEditSubmit": 28,
"routePanelWordFiltersDeleteSubmit": 29, "panel.WordFiltersDeleteSubmit": 29,
"panel.Pages": 30, "panel.Pages": 30,
"panel.PagesCreateSubmit": 31, "panel.PagesCreateSubmit": 31,
"panel.PagesEdit": 32, "panel.PagesEdit": 32,
@ -302,11 +302,11 @@ var reverseRouteMapEnum = map[int]string{
22: "panel.Settings", 22: "panel.Settings",
23: "panel.SettingEdit", 23: "panel.SettingEdit",
24: "panel.SettingEditSubmit", 24: "panel.SettingEditSubmit",
25: "routePanelWordFilters", 25: "panel.WordFilters",
26: "routePanelWordFiltersCreateSubmit", 26: "panel.WordFiltersCreateSubmit",
27: "routePanelWordFiltersEdit", 27: "panel.WordFiltersEdit",
28: "routePanelWordFiltersEditSubmit", 28: "panel.WordFiltersEditSubmit",
29: "routePanelWordFiltersDeleteSubmit", 29: "panel.WordFiltersDeleteSubmit",
30: "panel.Pages", 30: "panel.Pages",
31: "panel.PagesCreateSubmit", 31: "panel.PagesCreateSubmit",
32: "panel.PagesEdit", 32: "panel.PagesEdit",
@ -1070,7 +1070,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err = panel.SettingEditSubmit(w,req,user,extraData) err = panel.SettingEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/": case "/panel/settings/word-filters/":
counters.RouteViewCounter.Bump(25) counters.RouteViewCounter.Bump(25)
err = routePanelWordFilters(w,req,user) err = panel.WordFilters(w,req,user)
case "/panel/settings/word-filters/create/": case "/panel/settings/word-filters/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1079,10 +1079,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(26) counters.RouteViewCounter.Bump(26)
err = routePanelWordFiltersCreateSubmit(w,req,user) err = panel.WordFiltersCreateSubmit(w,req,user)
case "/panel/settings/word-filters/edit/": case "/panel/settings/word-filters/edit/":
counters.RouteViewCounter.Bump(27) counters.RouteViewCounter.Bump(27)
err = routePanelWordFiltersEdit(w,req,user,extraData) err = panel.WordFiltersEdit(w,req,user,extraData)
case "/panel/settings/word-filters/edit/submit/": case "/panel/settings/word-filters/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1091,7 +1091,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(28) counters.RouteViewCounter.Bump(28)
err = routePanelWordFiltersEditSubmit(w,req,user,extraData) err = panel.WordFiltersEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/delete/submit/": case "/panel/settings/word-filters/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1100,7 +1100,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(29) counters.RouteViewCounter.Bump(29)
err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData) err = panel.WordFiltersDeleteSubmit(w,req,user,extraData)
case "/panel/pages/": case "/panel/pages/":
err = common.AdminOnly(w,req,user) err = common.AdminOnly(w,req,user)
if err != nil { if err != nil {

View File

@ -326,6 +326,7 @@
"panel_perms_default":"Default", "panel_perms_default":"Default",
"panel_edit_button_text":"Edit", "panel_edit_button_text":"Edit",
"panel_update_button_text":"Update",
"panel_delete_button_text":"Delete", "panel_delete_button_text":"Delete",
"menu_forums_tooltip":"Forum List", "menu_forums_tooltip":"Forum List",

View File

@ -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 // 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? // TODO: Dynamically register these items to avoid maintaining as much code here?
func afterDBInit() (err error) { func afterDBInit() (err error) {
acc := qgen.Builder.Accumulator() acc := qgen.NewAcc()
common.Rstore, err = common.NewSQLReplyStore(acc) common.Rstore, err = common.NewSQLReplyStore(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
@ -102,13 +102,11 @@ func afterDBInit() (err error) {
return errors.WithStack(err) return errors.WithStack(err)
} }
log.Print("Loading the word filters") log.Print("Initialising the stores")
err = common.LoadWordFilters() common.WordFilters, err = common.NewDefaultWordFilterStore(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
log.Print("Initialising the stores")
common.MFAstore, err = common.NewSQLMFAStore(acc) common.MFAstore, err = common.NewSQLMFAStore(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)

View File

@ -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 // 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) { func TestSlugs(t *testing.T) {
var res string var res string
var msgList []MEPair var msgList []MEPair

View File

@ -171,135 +171,6 @@ func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.Use
return panelRenderTemplate("panel_dashboard", w, r, user, &pi) 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 { func routePanelGroups(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
header, stats, ferr := common.PanelUserCheck(w, r, &user) header, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -32,8 +32,7 @@ func execStmt(stmt *sql.Stmt, err error) error {
}*/ }*/
func eachUser(handle func(int) error) error { func eachUser(handle func(int) error) error {
acc := qgen.Builder.Accumulator() err := qgen.NewAcc().Select("users").Each(func(rows *sql.Rows) error {
err := acc.Select("users").Each(func(rows *sql.Rows) error {
var uid int var uid int
err := rows.Scan(&uid) err := rows.Scan(&uid)
if err != nil { if err != nil {

View File

@ -4,7 +4,16 @@ package main
import "./common" import "./common"
func init() { 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 { func initAdventure() error {

View File

@ -25,7 +25,7 @@ var bbcodeQuotes *regexp.Regexp
var bbcodeCode *regexp.Regexp var bbcodeCode *regexp.Regexp
func init() { 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 { func initBbcode() error {

View File

@ -1,8 +1,6 @@
package main package main
import ( import (
//"fmt"
"./common" "./common"
"./extend/guilds/lib" "./extend/guilds/lib"
"./query_gen/lib" "./query_gen/lib"
@ -12,7 +10,7 @@ import (
// TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin? // TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin?
func init() { 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? // TODO: Is it possible to avoid doing this when the plugin isn't activated?
common.PrebuildTmplList = append(common.PrebuildTmplList, guilds.PrebuildTmplList) common.PrebuildTmplList = append(common.PrebuildTmplList, guilds.PrebuildTmplList)
@ -38,7 +36,7 @@ func initGuilds() (err error) {
return err 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() guilds.ListStmt = acc.Select("guilds").Columns("guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime").Prepare()

View File

@ -3,7 +3,7 @@ package main
import "./common" import "./common"
func init() { 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 // init_heythere is separate from init() as we don't want the plugin to run if the plugin is disabled

View File

@ -20,7 +20,7 @@ var markdownStrikeTagOpen []byte
var markdownStrikeTagClose []byte var markdownStrikeTagClose []byte
func init() { 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 { func initMarkdown() error {

View File

@ -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. 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 } func initSkeleton() error { return nil }

View File

@ -8,6 +8,11 @@ import (
var LogPrepares = true 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 { type Accumulator struct {
conn *sql.DB conn *sql.DB
adapter Adapter adapter Adapter
@ -35,7 +40,7 @@ func (build *Accumulator) FirstError() error {
return build.firstErr return build.firstErr
} }
func (build *Accumulator) recordError(err error) { func (build *Accumulator) RecordError(err error) {
if err == nil { if err == nil {
return return
} }
@ -50,11 +55,11 @@ func (build *Accumulator) prepare(res string, err error) *sql.Stmt {
log.Print("res: ", res) log.Print("res: ", res)
} }
if err != nil { if err != nil {
build.recordError(err) build.RecordError(err)
return nil return nil
} }
stmt, err := build.conn.Prepare(res) stmt, err := build.conn.Prepare(res)
build.recordError(err) build.RecordError(err)
return stmt 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) { func (build *Accumulator) Tx(handler func(*TransactionBuilder) error) {
tx, err := build.conn.Begin() tx, err := build.conn.Begin()
if err != nil { if err != nil {
build.recordError(err) build.RecordError(err)
return return
} }
err = handler(&TransactionBuilder{tx, build.adapter, nil}) err = handler(&TransactionBuilder{tx, build.adapter, nil})
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
build.recordError(err) build.RecordError(err)
return 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 { 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) { func (build *Accumulator) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) {
if err != nil { if err != nil {
build.recordError(err) build.RecordError(err)
return nil return nil
} }
stmt, err = tx.Prepare(res) stmt, err = tx.Prepare(res)
build.recordError(err) build.RecordError(err)
return stmt return stmt
} }

View File

@ -284,8 +284,6 @@ func writeInserts(adapter qgen.Adapter) error {
build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse() build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse()
build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse()
return nil 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("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() build.Update("bumpSync").Table("sync").Set("last_update = UTC_TIMESTAMP()").Parse()
return nil 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("deleteActivityStreamMatch").Table("activity_stream_matches").Where("watcher = ? AND asid = ?").Parse()
//build.Delete("deleteActivityStreamMatchesByWatcher").Table("activity_stream_matches").Where("watcher = ?").Parse() //build.Delete("deleteActivityStreamMatchesByWatcher").Table("activity_stream_matches").Where("watcher = ?").Parse()
build.Delete("deleteWordFilter").Table("word_filters").Where("wfid = ?").Parse()
return nil return nil
} }

View File

@ -155,11 +155,11 @@ func buildPanelRoutes() {
View("panel.SettingEdit", "/panel/settings/edit/", "extraData"), View("panel.SettingEdit", "/panel/settings/edit/", "extraData"),
Action("panel.SettingEditSubmit", "/panel/settings/edit/submit/", "extraData"), Action("panel.SettingEditSubmit", "/panel/settings/edit/submit/", "extraData"),
View("routePanelWordFilters", "/panel/settings/word-filters/"), View("panel.WordFilters", "/panel/settings/word-filters/"),
Action("routePanelWordFiltersCreateSubmit", "/panel/settings/word-filters/create/"), Action("panel.WordFiltersCreateSubmit", "/panel/settings/word-filters/create/"),
View("routePanelWordFiltersEdit", "/panel/settings/word-filters/edit/", "extraData"), View("panel.WordFiltersEdit", "/panel/settings/word-filters/edit/", "extraData"),
Action("routePanelWordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extraData"), Action("panel.WordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extraData"),
Action("routePanelWordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extraData"), Action("panel.WordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extraData"),
View("panel.Pages", "/panel/pages/").Before("AdminOnly"), View("panel.Pages", "/panel/pages/").Before("AdminOnly"),
Action("panel.PagesCreateSubmit", "/panel/pages/create/submit/").Before("AdminOnly"), Action("panel.PagesCreateSubmit", "/panel/pages/create/submit/").Before("AdminOnly"),

View File

@ -319,8 +319,7 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user common.U
} }
// TODO: Add an EmailStore and move this there // TODO: Add an EmailStore and move this there
acc := qgen.Builder.Accumulator() _, err = qgen.NewAcc().Insert("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Exec(email, uid, 0, token)
_, err = acc.Insert("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Exec(email, uid, 0, token)
if err != nil { if err != nil {
return common.InternalError(err, w, r) 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") confirmPassword := r.PostFormValue("account-confirm-password")
// TODO: Use a reusable statement // TODO: Use a reusable statement
acc := qgen.Builder.Accumulator() err := qgen.NewAcc().Select("users").Columns("password, salt").Where("uid = ?").QueryRow(user.ID).Scan(&realPassword, &salt)
err := acc.Select("users").Columns("password, salt").Where("uid = ?").QueryRow(user.ID).Scan(&realPassword, &salt)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return common.LocalError("Your account no longer exists.", w, r, user) return common.LocalError("Your account no longer exists.", w, r, user)
} else if err != nil { } else if err != nil {

View File

@ -133,8 +133,7 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) co
common.DebugLog("in panel.AnalyticsViews") common.DebugLog("in panel.AnalyticsViews")
// TODO: Add some sort of analytics store / iterator? // TODO: Add some sort of analytics store / iterator?
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
common.DebugLog("in panel.AnalyticsRouteViews") common.DebugLog("in panel.AnalyticsRouteViews")
acc := qgen.Builder.Accumulator()
// TODO: Validate the route is valid // 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) agent = common.SanitiseSingleLine(agent)
common.DebugLog("in panel.AnalyticsAgentViews") common.DebugLog("in panel.AnalyticsAgentViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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") common.DebugLog("in panel.AnalyticsForumViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) system = common.SanitiseSingleLine(system)
common.DebugLog("in panel.AnalyticsSystemViews") common.DebugLog("in panel.AnalyticsSystemViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the OS name is valid // 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
@ -339,9 +334,8 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.
lang = common.SanitiseSingleLine(lang) lang = common.SanitiseSingleLine(lang)
common.DebugLog("in panel.AnalyticsLanguageViews") common.DebugLog("in panel.AnalyticsLanguageViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the language code is valid // 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
common.DebugLog("in panel.AnalyticsReferrerViews") common.DebugLog("in panel.AnalyticsReferrerViews")
acc := qgen.Builder.Accumulator()
// TODO: Verify the agent is valid // 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
common.DebugLog("in panel.AnalyticsTopics") common.DebugLog("in panel.AnalyticsTopics")
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
common.DebugLog("in panel.AnalyticsPosts") common.DebugLog("in panel.AnalyticsPosts")
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) return common.LocalError(err.Error(), w, r, user)
} }
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, forum").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks_forums").Columns("count, forum").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) return common.LocalError(err.Error(), w, r, user)
} }
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks").Columns("count, route").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) return common.LocalError(err.Error(), w, r, user)
} }
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks_agents").Columns("count, browser").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) return common.LocalError(err.Error(), w, r, user)
} }
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, system").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks_systems").Columns("count, system").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) return common.LocalError(err.Error(), w, r, user)
} }
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks_langs").Columns("count, lang").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) 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) return common.LocalError(err.Error(), w, r, user)
} }
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
rows, err := acc.Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -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
}

View File

@ -83,8 +83,7 @@ func PollResults(w http.ResponseWriter, r *http.Request, user common.User, sPoll
} }
// TODO: Abstract this // TODO: Abstract this
acc := qgen.Builder.Accumulator() rows, err := qgen.NewAcc().Select("polls_options").Columns("votes").Where("pollID = ?").Orderby("option ASC").Query(poll.ID)
rows, err := acc.Select("polls_options").Columns("votes").Where("pollID = ?").Orderby("option ASC").Query(poll.ID)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -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 // 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()*/ { 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 { if err != nil && err != sql.ErrNoRows {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }

View File

@ -9,12 +9,12 @@
<div id="panel_word_filters" class="colstack_item rowlist"> <div id="panel_word_filters" class="colstack_item rowlist">
{{range .Something}} {{range .Something}}
<div class="rowitem panel_compactrow editable_parent"> <div class="rowitem panel_compactrow editable_parent">
<a data-field="find" data-type="text" href="/panel/settings/word-filters/edit/{{.ID}}" class="editable_block panel_upshift edit_fields">{{.Find}}</a> <a data-field="find" data-type="text" href="/panel/settings/word-filters/edit/{{.ID}}" class="editable_block panel_upshift edit_fields filter_find">{{.Find}}</a>
<span class="itemSeparator"></span> <span class="itemSeparator"></span>
<a data-field="replacement" data-type="text" class="editable_block panel_compacttext">{{.Replacement}}</a> <a data-field="replacement" data-type="text" class="editable_block panel_compacttext filter_replace">{{.Replacement}}</a>
<span class="panel_buttons"> <span class="panel_buttons">
<a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" aria-label="{{lang "panel_word_filters_edit_button_aria"}}"></a> <a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" aria-label="{{lang "panel_word_filters_edit_button_aria"}}"></a>
<a class="panel_right_button" href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit show_on_edit' type='submit'>{{lang "panel_word_filters_update_button"}}</button></a> <a class="panel_right_button show_on_edit" href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel_word_filters_update_button"}}</button></a>
<a href="/panel/settings/word-filters/delete/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button hide_on_edit delete_button" aria-label="{{lang "panel_word_filters_delete_button_aria"}}"></a> <a href="/panel/settings/word-filters/delete/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button hide_on_edit delete_button" aria-label="{{lang "panel_word_filters_delete_button_aria"}}"></a>
</span> </span>
</div> </div>

View File

@ -414,7 +414,7 @@ h2 {
display: block; display: block;
} }
input, select, button, .formbutton, textarea { input, select, button, .formbutton, .panel_right_button, textarea {
border-radius: 3px; border-radius: 3px;
background: rgb(90,90,90); background: rgb(90,90,90);
color: rgb(200,200,200); color: rgb(200,200,200);
@ -434,7 +434,7 @@ input {
padding-bottom: 3px; padding-bottom: 3px;
font-size: 16px; font-size: 16px;
} }
button, .formbutton { button, .formbutton, .panel_right_button {
background: rgb(110,110,210); background: rgb(110,110,210);
color: rgb(250,250,250); color: rgb(250,250,250);
font-family: "Segoe UI"; font-family: "Segoe UI";

View File

@ -83,7 +83,7 @@
padding: 12px; padding: 12px;
} }
.to_right { .to_right, .panel_buttons {
margin-left: auto; margin-left: auto;
} }
@ -118,9 +118,18 @@ button, .formbutton {
/*background: rgb(110,110,210); /*background: rgb(110,110,210);
color: rgb(250,250,250);*/ color: rgb(250,250,250);*/
} }
button, .formbutton { button, .formbutton, .panel_right_button {
background: rgb(100,100,200); 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 { #themeSelector select {
background: rgb(90,90,90); background: rgb(90,90,90);
color: rgb(200,200,200); color: rgb(200,200,200);