Finish up the adminlogs.

Throw an error instead of updating a word filter that doesn't exist.
Track a bit of extra data for word filter updates.

Shorten the prefixes of the adminlog phrases from panel_logs_administration_ to panel_logs_admin_
Add a bunch of new adminlog phrases.
Tweak the word filter phrases.

Add the extra column to the moderation_logs and administration_logs tables.

You need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2019-11-10 12:37:53 +10:00
parent d03c0faf42
commit 1044518e74
23 changed files with 271 additions and 172 deletions

View File

@ -167,6 +167,7 @@ func createTables(adapter qgen.Adapter) (err error) {
)
// TODO: Should we add a users prefix to this table to fit the "unofficial convention"?
// TODO: Add an autoincrement key?
createTable("emails", "", "",
[]tC{
tC{"email", "varchar", 200, false, false, ""},
@ -620,6 +621,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""},
}, nil,
)
@ -631,6 +633,7 @@ func createTables(adapter qgen.Adapter) (err error) {
tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"actorID", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"doneAt", "datetime", 0, false, false, ""},
tC{"extra", "text", 0, false, false, ""},
}, nil,
)

View File

@ -4,7 +4,7 @@ import (
"database/sql"
"time"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
)
var ModLogs LogStore
@ -17,12 +17,14 @@ type LogItem struct {
IP string
ActorID int
DoneAt string
Extra string
}
type LogStore interface {
Create(action string, elementID int, elementType string, ip string, actorID int) (err error)
CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error)
Count() int
GetOffset(offset int, perPage int) (logs []LogItem, err error)
GetOffset(offset, perPage int) (logs []LogItem, err error)
}
type SQLModLogStore struct {
@ -34,15 +36,19 @@ type SQLModLogStore struct {
func NewModLogStore(acc *qgen.Accumulator) (*SQLModLogStore, error) {
ml := "moderation_logs"
return &SQLModLogStore{
create: acc.Insert(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
create: acc.Insert(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
count: acc.Count(ml).Prepare(),
getOffset: acc.Select(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Prepare(),
getOffset: acc.Select(ml).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError()
}
// TODO: Make a store for this?
func (s *SQLModLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID)
return s.CreateExtra(action, elementID, elementType, ip, actorID, "")
}
func (s *SQLModLogStore) CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID, extra)
return err
}
@ -58,7 +64,7 @@ func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
for rows.Next() {
var l LogItem
var doneAt time.Time
err := rows.Scan(&l.Action, &l.ElementID, &l.ElementType, &l.IP, &l.ActorID, &doneAt)
err := rows.Scan(&l.Action, &l.ElementID, &l.ElementType, &l.IP, &l.ActorID, &doneAt, &l.Extra)
if err != nil {
return logs, err
}
@ -68,7 +74,7 @@ func buildLogList(rows *sql.Rows) (logs []LogItem, err error) {
return logs, rows.Err()
}
func (s *SQLModLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) {
func (s *SQLModLogStore) GetOffset(offset, perPage int) (logs []LogItem, err error) {
rows, err := s.getOffset.Query(offset, perPage)
if err != nil {
return logs, err
@ -86,15 +92,19 @@ type SQLAdminLogStore struct {
func NewAdminLogStore(acc *qgen.Accumulator) (*SQLAdminLogStore, error) {
al := "administration_logs"
return &SQLAdminLogStore{
create: acc.Insert(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Fields("?,?,?,?,?,UTC_TIMESTAMP()").Prepare(),
create: acc.Insert(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Fields("?,?,?,?,?,UTC_TIMESTAMP(),?").Prepare(),
count: acc.Count(al).Prepare(),
getOffset: acc.Select(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt").Orderby("doneAt DESC").Limit("?,?").Prepare(),
getOffset: acc.Select(al).Columns("action, elementID, elementType, ipaddress, actorID, doneAt, extra").Orderby("doneAt DESC").Limit("?,?").Prepare(),
}, acc.FirstError()
}
// TODO: Make a store for this?
func (s *SQLAdminLogStore) Create(action string, elementID int, elementType string, ip string, actorID int) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID)
return s.CreateExtra(action, elementID, elementType, ip, actorID, "")
}
func (s *SQLAdminLogStore) CreateExtra(action string, elementID int, elementType string, ip string, actorID int, extra string) (err error) {
_, err = s.create.Exec(action, elementID, elementType, ip, actorID, extra)
return err
}
@ -106,7 +116,7 @@ func (s *SQLAdminLogStore) Count() (count int) {
return count
}
func (s *SQLAdminLogStore) GetOffset(offset int, perPage int) (logs []LogItem, err error) {
func (s *SQLAdminLogStore) GetOffset(offset, perPage int) (logs []LogItem, err error) {
rows, err := s.getOffset.Query(offset, perPage)
if err != nil {
return logs, err

View File

@ -321,11 +321,11 @@ type Plugin struct {
Installable bool
Installed bool
Init func(plugin *Plugin) error
Activate func(plugin *Plugin) error
Deactivate func(plugin *Plugin) // TODO: We might want to let this return an error?
Install func(plugin *Plugin) error
Uninstall func(plugin *Plugin) error // TODO: I'm not sure uninstall is implemented
Init func(pl *Plugin) error
Activate func(pl *Plugin) error
Deactivate func(pl *Plugin) // TODO: We might want to let this return an error?
Install func(pl *Plugin) error
Uninstall func(pl *Plugin) 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

View File

@ -470,7 +470,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
return ""
}
for _, filter := range wordFilters {
msg = strings.Replace(msg, filter.Find, filter.Replacement, -1)
msg = strings.Replace(msg, filter.Find, filter.Replace, -1)
}
// Search for URLs, mentions and hashlinks in the messages...

View File

@ -50,40 +50,40 @@ type Widget struct {
Literal bool
TickMask atomic.Value
InitFunc func(widget *Widget, schedule *WidgetScheduler) error
ShutdownFunc func(widget *Widget) error
BuildFunc func(widget *Widget, hvars interface{}) (string, error)
TickFunc func(widget *Widget) error
InitFunc func(w *Widget, schedule *WidgetScheduler) error
ShutdownFunc func(w *Widget) error
BuildFunc func(w *Widget, hvars interface{}) (string, error)
TickFunc func(w *Widget) error
}
func (widget *Widget) Delete() error {
_, err := widgetStmts.delete.Exec(widget.ID)
func (w *Widget) Delete() error {
_, err := widgetStmts.delete.Exec(w.ID)
if err != nil {
return err
}
// Reload the dock
// TODO: Better synchronisation
Widgets.delete(widget.ID)
widgets, err := getDockWidgets(widget.Side)
Widgets.delete(w.ID)
widgets, err := getDockWidgets(w.Side)
if err != nil {
return err
}
setDock(widget.Side, widgets)
setDock(w.Side, widgets)
return nil
}
func (widget *Widget) Copy() (owidget *Widget) {
func (w *Widget) Copy() (owidget *Widget) {
owidget = &Widget{}
*owidget = *widget
*owidget = *w
return owidget
}
// TODO: Test this
// TODO: Add support for zone:id. Perhaps, carry a ZoneID property around in *Header? It might allow some weirdness like frontend[5] which matches any zone with an ID of 5 but it would be a tad faster than verifying each zone, although it might be problematic if users end up relying on this behaviour for areas which don't pass IDs to the widgets system but *probably* should
// TODO: Add a selector which also matches topics inside a specific forum?
func (widget *Widget) Allowed(zone string, zoneid int) bool {
for _, loc := range strings.Split(widget.Location, "|") {
func (w *Widget) Allowed(zone string, zoneid int) bool {
for _, loc := range strings.Split(w.Location, "|") {
if len(loc) == 0 {
continue
}
@ -107,16 +107,15 @@ func (widget *Widget) Allowed(zone string, zoneid int) bool {
}
// TODO: Refactor
func (widget *Widget) Build(hvars interface{}) (string, error) {
if widget.Literal {
return widget.Body, nil
func (w *Widget) Build(hvars interface{}) (string, error) {
if w.Literal {
return w.Body, nil
}
if widget.BuildFunc != nil {
return widget.BuildFunc(widget, hvars)
if w.BuildFunc != nil {
return w.BuildFunc(w, hvars)
}
header := hvars.(*Header)
err := header.Theme.RunTmpl(widget.Body, hvars, header.Writer)
err := header.Theme.RunTmpl(w.Body, hvars, header.Writer)
return "", err
}
@ -125,40 +124,42 @@ type WidgetEdit struct {
Data map[string]string
}
func (widget *WidgetEdit) Create() error {
data, err := json.Marshal(widget.Data)
func (w *WidgetEdit) Create() (int, error) {
data, err := json.Marshal(w.Data)
if err != nil {
return 0, err
}
res, err := widgetStmts.create.Exec(w.Position, w.Side, w.Type, w.Enabled, w.Location, data)
if err != nil {
return 0, err
}
// Reload the dock
widgets, err := getDockWidgets(w.Side)
if err != nil {
return 0, err
}
setDock(w.Side, widgets)
wid64, err := res.LastInsertId()
return int(wid64), err
}
func (w *WidgetEdit) Commit() error {
data, err := json.Marshal(w.Data)
if err != nil {
return err
}
_, err = widgetStmts.create.Exec(widget.Position, widget.Side, widget.Type, widget.Enabled, widget.Location, data)
_, err = widgetStmts.update.Exec(w.Position, w.Side, w.Type, w.Enabled, w.Location, data, w.ID)
if err != nil {
return err
}
// Reload the dock
widgets, err := getDockWidgets(widget.Side)
widgets, err := getDockWidgets(w.Side)
if err != nil {
return err
}
setDock(widget.Side, widgets)
return nil
}
func (widget *WidgetEdit) Commit() error {
data, err := json.Marshal(widget.Data)
if err != nil {
return err
}
_, err = widgetStmts.update.Exec(widget.Position, widget.Side, widget.Type, widget.Enabled, widget.Location, data, widget.ID)
if err != nil {
return err
}
// Reload the dock
widgets, err := getDockWidgets(widget.Side)
if err != nil {
return err
}
setDock(widget.Side, widgets)
setDock(w.Side, widgets)
return nil
}

View File

@ -11,7 +11,13 @@ import (
type WordFilter struct {
ID int
Find string
Replacement string
Replace string
}
type WordFilterDiff struct {
BeforeFind string
BeforeReplace string
AfterFind string
AfterReplace string
}
var WordFilters WordFilterStore
@ -82,7 +88,7 @@ func (s *DefaultWordFilterStore) bypassGetAll() (filters []*WordFilter, err erro
for rows.Next() {
f := &WordFilter{ID: 0}
err := rows.Scan(&f.ID, &f.Find, &f.Replacement)
err := rows.Scan(&f.ID, &f.Find, &f.Replace)
if err != nil {
return filters, err
}
@ -98,7 +104,7 @@ func (s *DefaultWordFilterStore) GetAll() (filters map[int]*WordFilter, err erro
func (s *DefaultWordFilterStore) Get(id int) (*WordFilter, error) {
wf := &WordFilter{ID: id}
err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replacement)
err := s.get.QueryRow(id).Scan(&wf.Find, &wf.Replace)
return wf, err
}

View File

@ -16,14 +16,14 @@ func init() {
})
}
func initAdventure(plugin *c.Plugin) error {
func initAdventure(pl *c.Plugin) error {
return nil
}
// TODO: Change the signature to return an error?
func deactivateAdventure(plugin *c.Plugin) {
func deactivateAdventure(pl *c.Plugin) {
}
func installAdventure(plugin *c.Plugin) error {
func installAdventure(pl *c.Plugin) error {
return nil
}

View File

@ -1024,26 +1024,36 @@
"page_unknown":"Unknown",
"setting_unknown":"unknown",
"panel_logs_administration_head":"Admin Action Logs",
"panel_logs_administration_action_user_edit":"User <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_create":"Group <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_edit":"Group <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_promotion_create":"A group promotion was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_group_promotion_delete":"A group promotion was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_reorder":"Forums were reordered by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_create":"Forum <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_delete":"Forum <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_forum_edit":"Forum <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_create":"Page <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_delete":"Page <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_page_edit":"Page <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_setting_edit":"Setting <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_create":"Word Filter <a href='%s'>%d</a> was created by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_delete":"Word Filter <a href='%s'>%d</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_administration_action_word_filter_edit":"Word Filter <a href='%s'>%d</a> was modified by <a href='%s'>%s</a>",
"panel_logs_administration_action_backup_download":"A backup was downloaded by <a href='%s'>%s</a>",
"panel_logs_administration_action_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>",
"panel_logs_administration_no_logs":"There aren't any events logged.",
"panel_logs_admin_head":"Admin Action Logs",
"panel_logs_admin_action_user_edit":"User <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_group_create":"Group <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_group_edit":"Group <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_group_promotion_create":"A group promotion was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_group_promotion_delete":"A group promotion was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_forum_reorder":"Forums were reordered by <a href='%s'>%s</a>",
"panel_logs_admin_action_forum_create":"Forum <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_forum_delete":"Forum <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_forum_edit":"Forum <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_page_create":"Page <a href='%s'>%s</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_page_delete":"Page <a href='%s'>%s</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_page_edit":"Page <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_setting_edit":"Setting <a href='%s'>%s</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_word_filter_create":"A word filter was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_word_filter_delete":"A word filter was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_word_filter_edit":"A word filter was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_menu_suborder":"Menu #%d was reordered by <a href='%s'>%s</a>",
"panel_logs_admin_action_menu_item_edit":"Menu item <a href='%s'>#%d</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_menu_item_create":"Menu item <a href='%s'>#%d</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_menu_item_delete":"Menu item <a href='%s'>#%d</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_widget_edit":"Widget <a href='%s'>#%d</a> was modified by <a href='%s'>%s</a>",
"panel_logs_admin_action_widget_create":"Widget <a href='%s'>#%d</a> was created by <a href='%s'>%s</a>",
"panel_logs_admin_action_widget_delete":"Widget <a href='%s'>#%d</a> was deleted by <a href='%s'>%s</a>",
"panel_logs_admin_action_plugin_activate":"The plugin '%s' was activated by <a href='%s'>%s</a>",
"panel_logs_admin_action_plugin_deactivate":"The plugin '%s' was deactivated by <a href='%s'>%s</a>",
"panel_logs_admin_action_plugin_install":"The plugin '%s' was installed by <a href='%s'>%s</a>",
"panel_logs_admin_action_backup_download":"A backup was downloaded by <a href='%s'>%s</a>",
"panel_logs_admin_action_unknown":"Unknown action '%s' on elementType '%s' by <a href='%s'>%s</a>",
"panel_logs_admin_no_logs":"There aren't any events logged.",
"panel_plugins_head":"Plugins",
"panel_plugins_author_prefix":"Author: ",

View File

@ -1355,13 +1355,13 @@ func TestWordFilters(t *testing.T) {
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")
expect(t, filter.Replace == "lovely", "Word filter replacement should be lovely")
filter, err = c.WordFilters.Get(1)
expectNilErr(t, err)
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")
expect(t, filter.Replace == "lovely", "Word filter replacement should be lovely")
// Update
expectNilErr(t, c.WordFilters.Update(1, "b", "a"))
@ -1376,13 +1376,13 @@ func TestWordFilters(t *testing.T) {
filter = filters[1]
expect(t, filter.ID == 1, "Word filter ID should be 1")
expect(t, filter.Find == "b", "Word filter needle should be b")
expect(t, filter.Replacement == "a", "Word filter replacement should be a")
expect(t, filter.Replace == "a", "Word filter replacement should be a")
filter, err = c.WordFilters.Get(1)
expectNilErr(t, err)
expect(t, filter.ID == 1, "Word filter ID should be 1")
expect(t, filter.Find == "b", "Word filter needle should be imbecile")
expect(t, filter.Replacement == "a", "Word filter replacement should be a")
expect(t, filter.Replace == "a", "Word filter replacement should be a")
// TODO: Add a test for ParseMessage relating to word filters
@ -1445,8 +1445,9 @@ func TestWidgets(t *testing.T) {
widget := &c.Widget{Position: 0, Side: "rightSidebar", Type: "simple", Enabled: true, Location: "global"}
ewidget := &c.WidgetEdit{widget, map[string]string{"Name": "Test", "Text": "Testing"}}
err = ewidget.Create()
wid, err := ewidget.Create()
expectNilErr(t, err)
expect(t,wid==1,"wid should be 1")
// TODO: Do a test for the widget body
widget2, err := c.Widgets.Get(1)

View File

@ -130,8 +130,10 @@ func patcher(scanner *bufio.Scanner) error {
}
err := patch(scanner)
if err != nil {
fmt.Println("Failed to apply patch "+strconv.Itoa(index+1))
return err
}
fmt.Println("Applied patch "+strconv.Itoa(index+1))
patched++
}
@ -140,6 +142,8 @@ func patcher(scanner *bufio.Scanner) error {
if err != nil {
return err
}
} else {
fmt.Println("No new patches found.")
}
return nil

View File

@ -11,6 +11,7 @@ import (
type tblColumn = qgen.DBTableColumn
type tC = tblColumn
type tblKey = qgen.DBTableKey
type tK = tblKey
func init() {
addPatch(0, patch0)
@ -40,6 +41,7 @@ func init() {
addPatch(24, patch24)
addPatch(25, patch25)
addPatch(26, patch26)
addPatch(27, patch27)
}
func patch0(scanner *bufio.Scanner) (err error) {
@ -56,8 +58,8 @@ func patch0(scanner *bufio.Scanner) (err error) {
[]tC{
tC{"mid", "int", 0, false, true, ""},
},
[]tblKey{
tblKey{"mid", "primary", "", false},
[]tK{
tK{"mid", "primary", "", false},
},
))
if err != nil {
@ -83,8 +85,8 @@ func patch0(scanner *bufio.Scanner) (err error) {
tC{"staffOnly", "boolean", 0, false, false, "0"},
tC{"adminOnly", "boolean", 0, false, false, "0"},
},
[]tblKey{
tblKey{"miid", "primary", "", false},
[]tK{
tK{"miid", "primary", "", false},
},
))
if err != nil {
@ -190,8 +192,8 @@ func patch3(scanner *bufio.Scanner) error {
tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"doneAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"rlid", "primary", "", false},
[]tK{
tK{"rlid", "primary", "", false},
},
))
}
@ -253,8 +255,8 @@ func patch4(scanner *bufio.Scanner) error {
tC{"allowedGroups", "text", 0, false, false, ""},
tC{"menuID", "int", 0, false, false, "-1"},
},
[]tblKey{
tblKey{"pid", "primary", "", false},
[]tK{
tK{"pid", "primary", "", false},
},
))
if err != nil {
@ -277,7 +279,7 @@ func patch5(scanner *bufio.Scanner) error {
return err
}
err = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path = '/user/edit/'", "path = '/user/edit/critical/'"))
err = execStmt(qgen.Builder.SimpleUpdate("menu_items", "path='/user/edit/'", "path='/user/edit/critical/'"))
if err != nil {
return err
}
@ -296,8 +298,8 @@ func patch5(scanner *bufio.Scanner) error {
tC{"scratch8", "varchar", 50, false, false, ""},
tC{"createdAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"uid", "primary", "", false},
[]tK{
tK{"uid", "primary", "", false},
},
))
if err != nil {
@ -316,8 +318,8 @@ func patch7(scanner *bufio.Scanner) error {
[]tC{
tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
},
[]tblKey{
tblKey{"uid", "primary", "", false},
[]tK{
tK{"uid", "primary", "", false},
},
))
}
@ -378,8 +380,7 @@ func patch8(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("updates", "", "",
[]tC{
tC{"dbVersion", "int", 0, false, false, "0"},
},
[]tblKey{},
}, nil,
))
}
@ -398,8 +399,8 @@ func patch9(scanner *bufio.Scanner) error {
tC{"ipaddress", "varchar", 200, false, false, ""},
tC{"doneAt", "createdAt", 0, false, false, ""},
},
[]tblKey{
tblKey{"lid", "primary", "", false},
[]tK{
tK{"lid", "primary", "", false},
},
))
}
@ -461,23 +462,21 @@ func patch11(scanner *bufio.Scanner) error {
// We could probably do something more efficient, but as there shouldn't be too many sites right now, we can probably cheat a little, otherwise it'll take forever to get things done
return acc().Select("topics").Cols("tid").EachInt(func(tid int) error {
stid := itoa(tid)
count, err := acc().Count("attachments").Where("originTable = 'topics' and originID = " + stid).Total()
count, err := acc().Count("attachments").Where("originTable='topics' and originID=" + stid).Total()
if err != nil {
return err
}
_, err = acc().Update("topics").Set("attachCount = ?").Where("tid = " + stid).Exec(count)
return err
})
/*return acc().Select("replies").Cols("rid").EachInt(func(rid int) error {
srid := itoa(rid)
count, err := acc().Count("attachments").Where("originTable = 'replies' and originID = " + srid).Total()
count, err := acc().Count("attachments").Where("originTable='replies' and originID=" + srid).Total()
if err != nil {
return err
}
_, err = acc().Update("replies").Set("attachCount = ?").Where("rid = " + srid).Exec(count)
_, err = acc().Update("replies").Set("attachCount = ?").Where("rid=" + srid).Exec(count)
return err
})*/
}
@ -519,24 +518,19 @@ func patch12(scanner *bufio.Scanner) error {
}
func patch13(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("widgets", tC{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary", "", false}))
if err != nil {
return err
}
return nil
return execStmt(qgen.Builder.AddColumn("widgets", tC{"wid", "int", 0, false, true, ""}, &tK{"wid", "primary", "", false}))
}
func patch14(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddKey("topics", "title", tblKey{"title", "fulltext", "", false}))
err := execStmt(qgen.Builder.AddKey("topics", "title", tK{"title", "fulltext", "", false}))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddKey("topics", "content", tblKey{"content", "fulltext", "", false}))
err = execStmt(qgen.Builder.AddKey("topics", "content", tK{"content", "fulltext", "", false}))
if err != nil {
return err
}
err = execStmt(qgen.Builder.AddKey("replies", "content", tblKey{"content", "fulltext", "", false}))
err = execStmt(qgen.Builder.AddKey("replies", "content", tK{"content", "fulltext", "", false}))
if err != nil {
return err
}
@ -585,12 +579,10 @@ func patch17(scanner *bufio.Scanner) error {
if err != nil {
return err
}
err = acc().Select("topics").Cols("parentID").Where("tid = ?").QueryRow(tid).Scan(&sectionID)
if err != nil {
return err
}
_, err = acc().Update("attachments").Set("sectionID = ?, extra = ?").Where("originTable = 'replies' AND originID = ?").Exec(sectionID, tid, rid)
return err
})
@ -616,12 +608,10 @@ func patch20(scanner *bufio.Scanner) error {
if err != nil {
return err
}
err = acc().Select("activity_stream").Cols("asid").Where("asid = ?").QueryRow(asid).Scan(&asid)
if err != sql.ErrNoRows {
return err
}
_, err = acc().Delete("activity_stream_matches").Where("asid = ?").Run(asid)
return err
})
@ -673,8 +663,8 @@ func patch23(scanner *bufio.Scanner) error {
tC{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 0, false, false, ""},
},
[]tblKey{
tblKey{"cid", "primary", "", false},
[]tK{
tK{"cid", "primary", "", false},
},
))
if err != nil {
@ -693,8 +683,8 @@ func patch23(scanner *bufio.Scanner) error {
tC{"body", "varchar", 50, false, false, ""},
tC{"post", "varchar", 50, false, false, "''"},
},
[]tblKey{
tblKey{"pid", "primary", "", false},
[]tK{
tK{"pid", "primary", "", false},
},
))
if err != nil {
@ -729,8 +719,8 @@ func patch24(scanner *bufio.Scanner) error {
tC{"level", "int", 0, false, false, ""},
tC{"minTime", "int", 0, false, false, ""}, // How long someone needs to have been in their current group before being promoted
},
[]tblKey{
tblKey{"pid", "primary", "", false},
[]tK{
tK{"pid", "primary", "", false},
},
))
}
@ -747,3 +737,11 @@ func patch26(scanner *bufio.Scanner) error {
}, nil,
))
}
func patch27(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("moderation_logs", tC{"extra", "text", 0, false, false, ""}, nil))
if err != nil {
return err
}
return execStmt(qgen.Builder.AddColumn("administration_logs", tC{"extra", "text", 0, false, false, ""}, nil))
}

View File

@ -100,53 +100,59 @@ func modlogsElementType(action string, elementType string, elementID int, actor
return out
}
func adminlogsElementType(action string, elementType string, elementID int, actor *c.User) (out string) {
func adminlogsElementType(action string, elementType string, elementID int, actor *c.User, extra string) (out string) {
switch elementType {
// TODO: Record more detail for this, e.g. which field/s was changed
case "user":
targetUser := handleUnknownUser(c.Users.Get(elementID))
out = p.GetTmplPhrasef("panel_logs_administration_action_user_"+action, targetUser.Link, targetUser.Name, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_user_"+action, targetUser.Link, targetUser.Name, actor.Link, actor.Name)
case "group":
g, err := c.Groups.Get(elementID)
if err != nil {
g = &c.Group{Name: p.GetTmplPhrase("group_unknown")}
}
out = p.GetTmplPhrasef("panel_logs_administration_action_group_"+action, "/panel/groups/edit/"+strconv.Itoa(g.ID), g.Name, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_group_"+action, "/panel/groups/edit/"+strconv.Itoa(g.ID), g.Name, actor.Link, actor.Name)
case "group_promotion":
out = p.GetTmplPhrasef("panel_logs_administration_action_group_promotion_"+action, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_group_promotion_"+action, actor.Link, actor.Name)
case "forum":
f, err := c.Forums.Get(elementID)
if err != nil {
f = &c.Forum{Name: p.GetTmplPhrase("forum_unknown")}
}
if action == "reorder" {
out = p.GetTmplPhrasef("panel_logs_administration_action_forum_reorder", actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_forum_reorder", actor.Link, actor.Name)
} else {
out = p.GetTmplPhrasef("panel_logs_administration_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_forum_"+action, "/panel/forums/edit/"+strconv.Itoa(f.ID), f.Name, actor.Link, actor.Name)
}
case "page":
pp, err := c.Pages.Get(elementID)
if err != nil {
pp = &c.CustomPage{Name: p.GetTmplPhrase("page_unknown")}
}
out = p.GetTmplPhrasef("panel_logs_administration_action_page_"+action, "/panel/pages/edit/"+strconv.Itoa(pp.ID), pp.Name, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_page_"+action, "/panel/pages/edit/"+strconv.Itoa(pp.ID), pp.Name, actor.Link, actor.Name)
case "setting":
s, err := c.SettingBox.Load().(c.SettingMap).BypassGet(action)
if err != nil {
s = &c.Setting{Name: p.GetTmplPhrase("setting_unknown")}
}
out = p.GetTmplPhrasef("panel_logs_administration_action_setting_edit", "/panel/settings/edit/"+s.Name, s.Name, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_setting_edit", "/panel/settings/edit/"+s.Name, s.Name, actor.Link, actor.Name)
case "word_filter":
wf, err := c.WordFilters.Get(elementID)
if err != nil {
wf = &c.WordFilter{}
out = p.GetTmplPhrasef("panel_logs_admin_action_word_filter_"+action, actor.Link, actor.Name)
case "menu":
if action == "suborder" {
out = p.GetTmplPhrasef("panel_logs_admin_action_menu_suborder", elementID, actor.Link, actor.Name)
}
out = p.GetTmplPhrasef("panel_logs_administration_action_word_filter_"+action, "/panel/settings/word-filters/", wf.ID, actor.Link, actor.Name)
case "menu_item":
out = p.GetTmplPhrasef("panel_logs_admin_action_menu_item_"+action, "/panel/themes/menus/item/edit/"+strconv.Itoa(elementID), elementID, actor.Link, actor.Name)
case "widget":
out = p.GetTmplPhrasef("panel_logs_admin_action_widget_"+action, "/panel/themes/widgets/", elementID, actor.Link, actor.Name)
case "plugin":
out = p.GetTmplPhrasef("panel_logs_admin_action_plugin_"+action, extra, actor.Link, actor.Name)
case "backup":
out = p.GetTmplPhrasef("panel_logs_administration_action_backup_"+action, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_backup_"+action, actor.Link, actor.Name)
}
if out == "" {
out = p.GetTmplPhrasef("panel_logs_administration_action_unknown", action, elementType, actor.Link, actor.Name)
out = p.GetTmplPhrasef("panel_logs_admin_action_unknown", action, elementType, actor.Link, actor.Name)
}
return out
}
@ -182,8 +188,6 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
if ferr != nil {
return ferr
}
basePage.AddNotice("Currently under development")
page, _ := strconv.Atoi(r.FormValue("page"))
perPage := 12
offset, page, lastPage := c.PageOffset(c.AdminLogs.Count(), page, perPage)
@ -195,7 +199,7 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError
llist := make([]c.PageLogItem, len(logs))
for index, log := range logs {
actor := handleUnknownUser(c.Users.Get(log.ActorID))
action := adminlogsElementType(log.Action, log.ElementType, log.ElementID, actor)
action := adminlogsElementType(log.Action, log.ElementType, log.ElementID, actor, log.Extra)
llist[index] = c.PageLogItem{Action: template.HTML(action), IP: log.IP, DoneAt: log.DoneAt}
}

View File

@ -22,7 +22,7 @@ func Plugins(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
pluginList = append(pluginList, plugin)
}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage,"","","panel_plugins", c.PanelPage{basePage, pluginList, nil}})
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_plugins", c.PanelPage{basePage, pluginList, nil}})
}
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily
@ -73,6 +73,10 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user c.User, uname
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
err = c.AdminLogs.CreateExtra("activate", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
@ -107,6 +111,10 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user c.User, unam
if plugin.Deactivate != nil {
plugin.Deactivate(plugin)
}
err = c.AdminLogs.CreateExtra("deactivate", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil
@ -173,6 +181,10 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user c.User, uname s
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
err = c.AdminLogs.CreateExtra("install", 0, "plugin", user.LastIP, user.ID, c.SanitiseSingleLine(plugin.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
return nil

View File

@ -58,6 +58,10 @@ func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user c.User, uname
if err != nil {
return c.InternalError(err, w, r)
}
err = c.AdminLogs.CreateExtra("set_default", 0, "theme", user.LastIP, user.ID, c.SanitiseSingleLine(theme.Name))
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther)
return nil
@ -173,10 +177,10 @@ func themesMenuItemSetters(r *http.Request, i c.MenuItem) c.MenuItem {
i.Aria = getItem("aria")
i.Tooltip = getItem("tooltip")
i.TmplName = getItem("tmplname")
i.GuestOnly = false
switch getItem("permissions") {
case "everyone":
i.GuestOnly = false
i.MemberOnly = false
i.SuperModOnly = false
i.AdminOnly = false
@ -186,17 +190,14 @@ func themesMenuItemSetters(r *http.Request, i c.MenuItem) c.MenuItem {
i.SuperModOnly = false
i.AdminOnly = false
case "member-only":
i.GuestOnly = false
i.MemberOnly = true
i.SuperModOnly = false
i.AdminOnly = false
case "supermod-only":
i.GuestOnly = false
i.MemberOnly = true
i.SuperModOnly = true
i.AdminOnly = false
case "admin-only":
i.GuestOnly = false
i.MemberOnly = true
i.SuperModOnly = true
i.AdminOnly = true
@ -232,6 +233,11 @@ func ThemesMenuItemEditSubmit(w http.ResponseWriter, r *http.Request, user c.Use
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("edit", menuItem.ID, "menu_item", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js)
}
@ -240,11 +246,11 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user c.U
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageThemes {
return c.NoPermissionsJSQ(w, r, user, js)
}
smenuID := r.PostFormValue("mid")
if smenuID == "" {
return c.LocalErrorJSQ("No menuID provided", w, r, user, js)
@ -260,6 +266,11 @@ func ThemesMenuItemCreateSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("create", itemID, "menu_item", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/item/edit/"+strconv.Itoa(itemID), w, r, js)
}
@ -289,6 +300,11 @@ func ThemesMenuItemDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.U
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("delete", menuItem.ID, "menu_item", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/", w, r, js)
}
@ -326,6 +342,11 @@ func ThemesMenuItemOrderSubmit(w http.ResponseWriter, r *http.Request, user c.Us
}
menuHold.UpdateOrder(updateMap)
err = c.AdminLogs.Create("suborder", menuHold.MenuID, "menu", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/menus/edit/"+strconv.Itoa(mid), w, r, js)
}
@ -409,7 +430,6 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil {
return c.LocalErrorJSQ(p.GetErrorPhrase("id_must_be_integer"), w, r, user, js)
}
widget, err := c.Widgets.Get(wid)
if err == sql.ErrNoRows {
return c.NotFoundJSQ(w, r, nil, js)
@ -426,6 +446,10 @@ func ThemesWidgetsEditSubmit(w http.ResponseWriter, r *http.Request, user c.User
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("edit", widget.ID, "widget", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/widgets/", w, r, js)
}
@ -446,10 +470,14 @@ func ThemesWidgetsCreateSubmit(w http.ResponseWriter, r *http.Request, user c.Us
return c.LocalErrorJSQ(err.Error(), w, r, user, js)
}
err = ewidget.Create()
wid, err := ewidget.Create()
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("create", wid, "widget", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/widgets/", w, r, js)
}
@ -479,6 +507,10 @@ func ThemesWidgetsDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.Us
if err != nil {
return c.InternalError(err, w, r)
}
err = c.AdminLogs.Create("delete", widget.ID, "widget", user.LastIP, user.ID)
if err != nil {
return c.InternalError(err, w, r)
}
return successRedirect("/panel/themes/widgets/", w, r, js)
}

View File

@ -2,6 +2,7 @@ package panel
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
"strings"
@ -95,13 +96,24 @@ func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user c.User,
// 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
replace := strings.TrimSpace(r.PostFormValue("replace"))
wf, err := c.WordFilters.Get(wfid)
if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This word filter doesn't exist.", w, r, user, js)
} else if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.WordFilters.Update(wfid, find, replace)
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.Create("edit", wfid, "word_filter", user.LastIP, user.ID)
lBytes, err := json.Marshal(c.WordFilterDiff{wf.Find, wf.Replace, find, replace})
if err != nil {
return c.InternalError(err, w, r)
return c.InternalErrorJSQ(err, w, r, js)
}
err = c.AdminLogs.CreateExtra("edit", wfid, "word_filter", user.LastIP, user.ID, string(lBytes))
if err != nil {
return c.InternalErrorJSQ(err, w, r, js)
}
http.Redirect(w, r, "/panel/settings/word-filters/", http.StatusSeeOther)

View File

@ -4,5 +4,6 @@ CREATE TABLE [administration_logs] (
[elementType] nvarchar (100) not null,
[ipaddress] nvarchar (200) not null,
[actorID] int not null,
[doneAt] datetime not null
[doneAt] datetime not null,
[extra] nvarchar (MAX) not null
);

View File

@ -4,5 +4,6 @@ CREATE TABLE [moderation_logs] (
[elementType] nvarchar (100) not null,
[ipaddress] nvarchar (200) not null,
[actorID] int not null,
[doneAt] datetime not null
[doneAt] datetime not null,
[extra] nvarchar (MAX) not null
);

View File

@ -4,5 +4,6 @@ CREATE TABLE `administration_logs` (
`elementType` varchar(100) not null,
`ipaddress` varchar(200) not null,
`actorID` int not null,
`doneAt` datetime not null
`doneAt` datetime not null,
`extra` text not null
);

View File

@ -4,5 +4,6 @@ CREATE TABLE `moderation_logs` (
`elementType` varchar(100) not null,
`ipaddress` varchar(200) not null,
`actorID` int not null,
`doneAt` datetime not null
`doneAt` datetime not null,
`extra` text not null
);

View File

@ -4,5 +4,6 @@ CREATE TABLE "administration_logs" (
`elementType` varchar (100) not null,
`ipaddress` varchar (200) not null,
`actorID` int not null,
`doneAt` timestamp not null
`doneAt` timestamp not null,
`extra` text not null
);

View File

@ -4,5 +4,6 @@ CREATE TABLE "moderation_logs" (
`elementType` varchar (100) not null,
`ipaddress` varchar (200) not null,
`actorID` int not null,
`doneAt` timestamp not null
`doneAt` timestamp not null,
`extra` text not null
);

View File

@ -1,5 +1,5 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div>
<div class="rowitem"><h1>{{lang "panel_logs_admin_head"}}</h1></div>
</div>
<div id="panel_modlogs" class="colstack_item rowlist loglist">
{{range .Logs}}
@ -15,7 +15,7 @@
</div>
{{else}}
<div class="rowitem rowmsg">
<a>{{lang "panel_logs_administration_no_logs"}}</a>
<a>{{lang "panel_logs_admin_no_logs"}}</a>
</div>
{{end}}
</div>

View File

@ -6,7 +6,7 @@
<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 filter_find">{{.Find}}</a>
<span class="itemSeparator"></span>
<a data-field="replace" data-type="text" class="editable_block panel_compacttext filter_replace">{{.Replacement}}</a>
<a data-field="replace" data-type="text" class="editable_block panel_compacttext filter_replace">{{.Replace}}</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 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>