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
script: ./run-linux-tests
after_script:
- ls
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
addons:
mariadb: '10.0'

View File

@ -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()

View File

@ -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(),

View File

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

View File

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

View File

@ -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),

View File

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

View File

@ -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(),

View File

@ -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),

View File

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

View File

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

View File

@ -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(),

View File

@ -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),

View File

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

View File

@ -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(),

View File

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

View File

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

View File

@ -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(),

View File

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

View File

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

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>")
// 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)
}

View File

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

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
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?

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) {
/*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()
}

View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),
getAll *sql.Stmt
create *sql.Stmt
delete *sql.Stmt
update *sql.Stmt
count *sql.Stmt
}
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(),
}
return acc.FirstError()
})
// TODO: Should we initialise this elsewhere?
if acc.FirstError() == nil {
acc.RecordError(store.ReloadAll())
}
return store, acc.FirstError()
}
func LoadWordFilters() error {
var wordFilters = WordFilterMap(make(map[int]WordFilter))
filters, err := wordFilters.BypassGetAll()
// 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
}

View File

@ -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(),

View File

@ -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 {
@ -160,13 +141,5 @@ func _gen_mssql() (err error) {
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
}

View File

@ -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 {
@ -147,12 +130,5 @@ func _gen_mysql() (err error) {
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
}

View File

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

View File

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

View File

@ -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",

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
// 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)

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

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)
}
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 {

View File

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

View File

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

View File

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

View File

@ -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()

View File

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

View File

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

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.
*/
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 }

View File

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

View File

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

View File

@ -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"),

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
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 {

View File

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

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

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

View File

@ -9,12 +9,12 @@
<div id="panel_word_filters" class="colstack_item rowlist">
{{range .Something}}
<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>
<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">
<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>
</span>
</div>

View File

@ -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";

View File

@ -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);