mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-13 09:26:23 -07:00
Add filter hits page, refactor manage action resolving to use the router
This commit is contained in:
parent
a50845ee87
commit
69c8cc4765
16 changed files with 292 additions and 340 deletions
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/events"
|
||||
"github.com/gochan-org/gochan/pkg/manage"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
|
||||
|
@ -110,6 +111,7 @@ func main() {
|
|||
os.Exit(1) // skipcq: CRT-D0011
|
||||
}
|
||||
defer events.TriggerEvent("shutdown")
|
||||
manage.InitManagePages()
|
||||
go initServer()
|
||||
<-sc
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
"github.com/gochan-org/gochan/pkg/manage"
|
||||
"github.com/gochan-org/gochan/pkg/posting"
|
||||
"github.com/gochan-org/gochan/pkg/server"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
|
@ -30,9 +29,6 @@ func initServer() {
|
|||
router := server.GetRouter()
|
||||
router.GET(config.WebPath("/captcha"), bunrouter.HTTPHandlerFunc(posting.ServeCaptcha))
|
||||
router.POST(config.WebPath("/captcha"), bunrouter.HTTPHandlerFunc(posting.ServeCaptcha))
|
||||
router.GET(config.WebPath("/manage"), bunrouter.HTTPHandlerFunc(manage.CallManageFunction))
|
||||
router.GET(config.WebPath("/manage/:action"), bunrouter.HTTPHandlerFunc(manage.CallManageFunction))
|
||||
router.POST(config.WebPath("/manage/:action"), bunrouter.HTTPHandlerFunc(manage.CallManageFunction))
|
||||
router.GET(config.WebPath("/post"), bunrouter.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, config.WebPath("/"), http.StatusFound)
|
||||
}))
|
||||
|
|
|
@ -29,6 +29,10 @@ table.mgmt-table {
|
|||
text-align: center;
|
||||
width: 7%;
|
||||
}
|
||||
col.filter-actions, td.filter-actions {
|
||||
text-align: center;
|
||||
width: 10%;
|
||||
}
|
||||
col.row-date {
|
||||
width: 20%;
|
||||
}
|
||||
|
|
|
@ -328,6 +328,10 @@ table.mgmt-table td.table-actions, table.mgmt-table col.row-actions, table.mgmt-
|
|||
text-align: center;
|
||||
width: 7%;
|
||||
}
|
||||
table.mgmt-table col.filter-actions, table.mgmt-table td.filter-actions {
|
||||
text-align: center;
|
||||
width: 10%;
|
||||
}
|
||||
table.mgmt-table col.row-date {
|
||||
width: 20%;
|
||||
}
|
||||
|
|
|
@ -626,3 +626,26 @@ func ApplyFilter(filter *Filter, conditions []FilterCondition, boards []int) err
|
|||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// GetFilterHits returns an array of incidents where an attempted post matched a filter (excluding wordfilters)
|
||||
func GetFilterHits(filterID int) ([]FilterHit, error) {
|
||||
const querySQL = `SELECT id, match_time, post_data FROM DBPREFIXfilter_hits WHERE filter_id = ?`
|
||||
rows, cancel, err := QueryTimeoutSQL(nil, querySQL, filterID)
|
||||
defer cancel()
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var hits []FilterHit
|
||||
for rows.Next() {
|
||||
hit := FilterHit{FilterID: filterID}
|
||||
if err = rows.Scan(&hit.ID, &hit.MatchTime, &hit.PostData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hits = append(hits, hit)
|
||||
}
|
||||
return hits, rows.Close()
|
||||
}
|
||||
|
|
|
@ -163,10 +163,10 @@ func GetStaffBySession(session string) (*Staff, error) {
|
|||
JOIN DBPREFIXsessions as sessions ON sessions.staff_id = staff.id
|
||||
WHERE sessions.data = ?`
|
||||
|
||||
staff := new(Staff)
|
||||
var staff Staff
|
||||
err := QueryRowTimeoutSQL(nil, query, []any{session}, []any{
|
||||
&staff.ID, &staff.Username, &staff.PasswordChecksum, &staff.Rank, &staff.AddedOn, &staff.LastLogin})
|
||||
return staff, err
|
||||
return &staff, err
|
||||
}
|
||||
|
||||
func GetStaffByUsername(username string, onlyActive bool) (*Staff, error) {
|
||||
|
|
|
@ -25,26 +25,25 @@ const (
|
|||
ManageBans = "manage_bans.html"
|
||||
ManageBoards = "manage_boards.html"
|
||||
ManageDashboard = "manage_dashboard.html"
|
||||
// ManageFileBans = "manage_filebans.html"
|
||||
ManageFilters = "manage_filters.html"
|
||||
ManageFilterHits = "manage_filter_hits.html"
|
||||
ManageFixThumbnails = "manage_fixthumbnails.html"
|
||||
ManageIPSearch = "manage_ipsearch.html"
|
||||
ManageLogin = "manage_login.html"
|
||||
// ManageNameBans = "manage_namebans.html"
|
||||
ManageRecentPosts = "manage_recentposts.html"
|
||||
ManageReports = "manage_reports.html"
|
||||
ManageSections = "manage_sections.html"
|
||||
ManageStaff = "manage_staff.html"
|
||||
ManageTemplates = "manage_templateoverride.html"
|
||||
ManageThreadAttrs = "manage_threadattrs.html"
|
||||
ManageViewLog = "manage_viewlog.html"
|
||||
ManageWordfilters = "manage_wordfilters.html"
|
||||
MoveThreadPage = "movethreadpage.html"
|
||||
PageFooter = "page_footer.html"
|
||||
PageHeader = "page_header.html"
|
||||
PostEdit = "post_edit.html"
|
||||
PostFlag = "flag.html"
|
||||
ThreadPage = "threadpage.html"
|
||||
ManageRecentPosts = "manage_recentposts.html"
|
||||
ManageReports = "manage_reports.html"
|
||||
ManageSections = "manage_sections.html"
|
||||
ManageStaff = "manage_staff.html"
|
||||
ManageTemplates = "manage_templateoverride.html"
|
||||
ManageThreadAttrs = "manage_threadattrs.html"
|
||||
ManageViewLog = "manage_viewlog.html"
|
||||
ManageWordfilters = "manage_wordfilters.html"
|
||||
MoveThreadPage = "movethreadpage.html"
|
||||
PageFooter = "page_footer.html"
|
||||
PageHeader = "page_header.html"
|
||||
PostEdit = "post_edit.html"
|
||||
PostFlag = "flag.html"
|
||||
ThreadPage = "threadpage.html"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -90,12 +89,12 @@ var (
|
|||
ManageDashboard: {
|
||||
files: []string{"manage_dashboard.html"},
|
||||
},
|
||||
// ManageFileBans: {
|
||||
// files: []string{"manage_filebans.html"},
|
||||
// },
|
||||
ManageFilters: {
|
||||
files: []string{"manage_filters.html"},
|
||||
},
|
||||
ManageFilterHits: {
|
||||
files: []string{"manage_filter_hits.html"},
|
||||
},
|
||||
ManageFixThumbnails: {
|
||||
files: []string{"manage_fixthumbnails.html"},
|
||||
},
|
||||
|
@ -105,9 +104,6 @@ var (
|
|||
ManageLogin: {
|
||||
files: []string{"manage_login.html"},
|
||||
},
|
||||
// ManageNameBans: {
|
||||
// files: []string{"manage_namebans.html"},
|
||||
// },
|
||||
ManageRecentPosts: {
|
||||
files: []string{"manage_recentposts.html"},
|
||||
},
|
||||
|
@ -226,6 +222,7 @@ func GetTemplateList() []string {
|
|||
return templateList
|
||||
}
|
||||
|
||||
// ParseTemplate initializes a new template with the given name and parses it
|
||||
func ParseTemplate(name, tmplStr string) (*template.Template, error) {
|
||||
return template.New(name).Funcs(funcMap).Parse(tmplStr)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,13 @@ package manage
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/gochan-org/gochan/pkg/server"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/uptrace/bunrouter"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -76,12 +80,18 @@ func getAction(id string, rank int) *Action {
|
|||
}
|
||||
|
||||
func RegisterManagePage(id string, title string, permissions int, jsonOutput int, callback CallbackFunction) {
|
||||
actions = append(actions, Action{
|
||||
action := Action{
|
||||
ID: id,
|
||||
Title: title,
|
||||
Permissions: permissions,
|
||||
JSONoutput: jsonOutput,
|
||||
Callback: callback,
|
||||
}
|
||||
actions = append(actions, action)
|
||||
server.GetRouter().WithGroup(config.WebPath("/manage"), func(g *bunrouter.Group) {
|
||||
groupPath := path.Join("/", id, "/*actionArgs")
|
||||
g.GET(groupPath, setupManageFunction(&action))
|
||||
g.POST(groupPath, setupManageFunction(&action))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -680,78 +680,15 @@ func viewLogCallback(_ http.ResponseWriter, _ *http.Request, _ *gcsql.Staff, _ b
|
|||
}
|
||||
|
||||
func registerAdminPages() {
|
||||
actions = append(actions,
|
||||
Action{
|
||||
ID: "updateannouncements",
|
||||
Title: "Update staff announcements",
|
||||
Permissions: AdminPerms,
|
||||
JSONoutput: NoJSON,
|
||||
Callback: updateAnnouncementsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "boards",
|
||||
Title: "Boards",
|
||||
Permissions: AdminPerms,
|
||||
JSONoutput: NoJSON,
|
||||
Callback: boardsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "boardsections",
|
||||
Title: "Board sections",
|
||||
Permissions: AdminPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: boardSectionsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "cleanup",
|
||||
Title: "Cleanup",
|
||||
Permissions: AdminPerms,
|
||||
Callback: cleanupCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "fixthumbnails",
|
||||
Title: "Regenerate thumbnails",
|
||||
Permissions: AdminPerms,
|
||||
Callback: fixThumbnailsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "templates",
|
||||
Title: "Override templates",
|
||||
Permissions: AdminPerms,
|
||||
Callback: templatesCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "rebuildfront",
|
||||
Title: "Rebuild front page",
|
||||
Permissions: AdminPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: rebuildFrontCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "rebuildall",
|
||||
Title: "Rebuild everything",
|
||||
Permissions: AdminPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: rebuildAllCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "rebuildboards",
|
||||
Title: "Rebuild boards",
|
||||
Permissions: AdminPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: rebuildBoardsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "reparsehtml",
|
||||
Title: "Reparse HTML",
|
||||
Permissions: AdminPerms,
|
||||
Callback: reparseHTMLCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "viewlog",
|
||||
Title: "View log",
|
||||
Permissions: AdminPerms,
|
||||
Callback: viewLogCallback,
|
||||
},
|
||||
)
|
||||
RegisterManagePage("updateannouncements", "Update staff announcements", AdminPerms, NoJSON, updateAnnouncementsCallback)
|
||||
RegisterManagePage("boards", "Boards", AdminPerms, NoJSON, boardsCallback)
|
||||
RegisterManagePage("boardsections", "Board sections", AdminPerms, OptionalJSON, boardSectionsCallback)
|
||||
RegisterManagePage("cleanup", "Cleanup", AdminPerms, NoJSON, cleanupCallback)
|
||||
RegisterManagePage("fixthumbnails", "Regenerate thumbnails", AdminPerms, NoJSON, fixThumbnailsCallback)
|
||||
RegisterManagePage("templates", "Override templates", AdminPerms, NoJSON, templatesCallback)
|
||||
RegisterManagePage("rebuildfront", "Rebuild front page", AdminPerms, OptionalJSON, rebuildFrontCallback)
|
||||
RegisterManagePage("rebuildboards", "Rebuild boards", AdminPerms, OptionalJSON, rebuildBoardsCallback)
|
||||
RegisterManagePage("rebuildall", "Rebuild everything", AdminPerms, OptionalJSON, rebuildAllCallback)
|
||||
RegisterManagePage("reparsehtml", "Reparse HTML", AdminPerms, NoJSON, reparseHTMLCallback)
|
||||
RegisterManagePage("viewlog", "View log", AdminPerms, NoJSON, viewLogCallback)
|
||||
}
|
||||
|
|
|
@ -217,40 +217,9 @@ func staffCallback(writer http.ResponseWriter, request *http.Request, staff *gcs
|
|||
}
|
||||
|
||||
func registerJanitorPages() {
|
||||
actions = append(actions,
|
||||
Action{
|
||||
ID: "logout",
|
||||
Title: "Logout",
|
||||
Permissions: JanitorPerms,
|
||||
Callback: logoutCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "clearmysessions",
|
||||
Title: "Log me out everywhere",
|
||||
Permissions: JanitorPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: clearMySessionsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "recentposts",
|
||||
Title: "Recent posts",
|
||||
Permissions: JanitorPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: recentPostsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "announcements",
|
||||
Title: "Announcements",
|
||||
Permissions: JanitorPerms,
|
||||
JSONoutput: AlwaysJSON,
|
||||
Callback: announcementsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "staff",
|
||||
Title: "Staff",
|
||||
Permissions: JanitorPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: staffCallback,
|
||||
},
|
||||
)
|
||||
RegisterManagePage("logout", "Logout", JanitorPerms, NoJSON, logoutCallback)
|
||||
RegisterManagePage("clearmysessions", "Log me out everywhere", JanitorPerms, OptionalJSON, clearMySessionsCallback)
|
||||
RegisterManagePage("recentposts", "Recent posts", JanitorPerms, OptionalJSON, recentPostsCallback)
|
||||
RegisterManagePage("announcements", "Announcements", JanitorPerms, AlwaysJSON, announcementsCallback)
|
||||
RegisterManagePage("staff", "Staff", JanitorPerms, OptionalJSON, staffCallback)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package manage
|
|||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
@ -17,8 +18,10 @@ import (
|
|||
"github.com/gochan-org/gochan/pkg/gctemplates"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
"github.com/gochan-org/gochan/pkg/posting/uploads"
|
||||
"github.com/gochan-org/gochan/pkg/server"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/uptrace/bunrouter"
|
||||
)
|
||||
|
||||
// manage actions that require moderator-level permission go here
|
||||
|
@ -199,6 +202,46 @@ func appealsCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.
|
|||
return manageAppealsBuffer.String(), err
|
||||
}
|
||||
|
||||
func filterHitsCallback(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, _, errEv *zerolog.Event) (output any, err error) {
|
||||
params, _ := request.Context().Value("actionParams").(bunrouter.Params)
|
||||
filterIDStr := params.ByName("filterID")
|
||||
filterID, err := strconv.Atoi(filterIDStr)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Str("filterID", filterIDStr).Msg("Filter ID is not a valid integer")
|
||||
return nil, err
|
||||
}
|
||||
errEv.Int("filterID", filterID)
|
||||
|
||||
hits, err := gcsql.GetFilterHits(filterID)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to get filter hits")
|
||||
return nil, errors.New("unable to get list of filter hits")
|
||||
}
|
||||
m := make(map[string]any)
|
||||
var ba []byte
|
||||
for h := range hits {
|
||||
// un-minify the JSON data to make it more readable
|
||||
if err = json.Unmarshal([]byte(hits[h].PostData), &m); err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to unmarshal post data for filter hit")
|
||||
return nil, err
|
||||
}
|
||||
if ba, err = json.MarshalIndent(m, "", " "); err != nil {
|
||||
errEv.Err(err).Caller().RawJSON("postData", []byte(hits[h].PostData)).Msg("Unable to marshal un-minified post data")
|
||||
return nil, err
|
||||
}
|
||||
hits[h].PostData = string(ba)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err = serverutil.MinifyTemplate(gctemplates.ManageFilterHits, map[string]any{
|
||||
"hits": hits,
|
||||
}, &buf, "text/html"); err != nil {
|
||||
errEv.Err(err).Caller().Str("template", gctemplates.ManageFilterHits).Msg("Unable to render template")
|
||||
return nil, errors.New("unable to render filter hits page")
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
type filterField struct {
|
||||
Value string
|
||||
Text string
|
||||
|
@ -206,7 +249,7 @@ type filterField struct {
|
|||
hasSearchbox bool
|
||||
}
|
||||
|
||||
func filtersCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output any, err error) {
|
||||
func filtersCallback(writer http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output any, err error) {
|
||||
var boards []int
|
||||
data := map[string]any{
|
||||
"allBoards": gcsql.AllBoards,
|
||||
|
@ -909,67 +952,18 @@ func wordfiltersCallback(_ http.ResponseWriter, request *http.Request, staff *gc
|
|||
}
|
||||
|
||||
func registerModeratorPages() {
|
||||
actions = append(actions,
|
||||
Action{
|
||||
ID: "bans",
|
||||
Title: "Bans",
|
||||
Permissions: ModPerms,
|
||||
Callback: bansCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "appeals",
|
||||
Title: "Ban appeals",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: appealsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "filters",
|
||||
Title: "Post filters",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: NoJSON,
|
||||
Callback: filtersCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "ipsearch",
|
||||
Title: "IP Search",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: NoJSON,
|
||||
Callback: ipSearchCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "reports",
|
||||
Title: "Reports",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: reportsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "threadattrs",
|
||||
Title: "View/Update Thread Attributes",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: OptionalJSON,
|
||||
Callback: threadAttrsCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "postinfo",
|
||||
Title: "Post info",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: AlwaysJSON,
|
||||
Callback: postInfoCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "fingerprint",
|
||||
Title: "Get image/thumbnail fingerprint",
|
||||
Permissions: ModPerms,
|
||||
JSONoutput: AlwaysJSON,
|
||||
Callback: fingerprintCallback,
|
||||
},
|
||||
Action{
|
||||
ID: "wordfilters",
|
||||
Title: "Wordfilters",
|
||||
Permissions: ModPerms,
|
||||
Callback: wordfiltersCallback,
|
||||
},
|
||||
)
|
||||
RegisterManagePage("bans", "Bans", ModPerms, NoJSON, bansCallback)
|
||||
RegisterManagePage("appeals", "Ban appeals", ModPerms, OptionalJSON, appealsCallback)
|
||||
RegisterManagePage("filters", "Post filters", ModPerms, NoJSON, filtersCallback)
|
||||
server.GetRouter().GET(config.WebPath("/manage/filters/hits/:filterID"), setupManageFunction(&Action{
|
||||
ID: "filters/hits",
|
||||
Title: "Filter hits",
|
||||
Callback: filterHitsCallback,
|
||||
}))
|
||||
RegisterManagePage("ipsearch", "IP Search", ModPerms, NoJSON, ipSearchCallback)
|
||||
RegisterManagePage("reports", "Reports", ModPerms, OptionalJSON, reportsCallback)
|
||||
RegisterManagePage("threadattrs", "View/Update Thread Attributes", ModPerms, OptionalJSON, threadAttrsCallback)
|
||||
RegisterManagePage("postinfo", "Post info", ModPerms, AlwaysJSON, postInfoCallback)
|
||||
RegisterManagePage("fingerprint", "Get image/thumbnail fingerprint", ModPerms, AlwaysJSON, fingerprintCallback)
|
||||
RegisterManagePage("wordfilters", "Wordfilters", ModPerms, NoJSON, wordfiltersCallback)
|
||||
}
|
||||
|
|
|
@ -95,12 +95,5 @@ func staffInfoCallback(_ http.ResponseWriter, _ *http.Request, staff *gcsql.Staf
|
|||
}
|
||||
|
||||
func registerNoPermPages() {
|
||||
actions = append(actions, loginAction,
|
||||
Action{
|
||||
ID: "staffinfo",
|
||||
Permissions: NoPerms,
|
||||
JSONoutput: AlwaysJSON,
|
||||
Callback: staffInfoCallback,
|
||||
},
|
||||
)
|
||||
RegisterManagePage("staffinfo", "", NoPerms, AlwaysJSON, staffInfoCallback)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ package manage
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
|
@ -28,134 +27,129 @@ func (esa *ErrStaffAction) Error() string {
|
|||
}
|
||||
|
||||
func serveError(writer http.ResponseWriter, field string, action string, message string, isJSON bool) {
|
||||
server.ServeError(writer, message, isJSON, map[string]interface{}{
|
||||
server.ServeError(writer, message, isJSON, map[string]any{
|
||||
"error": field,
|
||||
"action": action,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
// CallManageFunction is called when a user accesses /manage to use manage tools
|
||||
// or log in to a staff account
|
||||
func CallManageFunction(writer http.ResponseWriter, request *http.Request) {
|
||||
var err error
|
||||
wantsJSON := serverutil.IsRequestingJSON(request)
|
||||
accessEv := gcutil.LogAccess(request)
|
||||
infoEv, errEv := gcutil.LogRequest(request)
|
||||
defer gcutil.LogDiscard(infoEv, accessEv, errEv)
|
||||
// setupManageFunction returns a function used by the HTTP router to serve the action callback's return value
|
||||
func setupManageFunction(action *Action) bunrouter.HandlerFunc {
|
||||
return func(writer http.ResponseWriter, req bunrouter.Request) (err error) {
|
||||
request := req.Request
|
||||
wantsJSON := serverutil.IsRequestingJSON(request)
|
||||
accessEv := gcutil.LogAccess(request)
|
||||
infoEv, errEv := gcutil.LogRequest(request)
|
||||
defer gcutil.LogDiscard(infoEv, accessEv, errEv)
|
||||
|
||||
if err = request.ParseForm(); err != nil {
|
||||
errEv.Err(err).Caller().Msg("Error parsing form data")
|
||||
server.ServeError(writer, "Error parsing form data: "+err.Error(), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
params := bunrouter.ParamsFromContext(request.Context())
|
||||
actionID := params.ByName("action")
|
||||
gcutil.LogStr("action", actionID, infoEv, accessEv, errEv)
|
||||
|
||||
var staff *gcsql.Staff
|
||||
staff, err = GetStaffFromRequest(request)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
errEv.Err(err).Caller().
|
||||
Str("request", "getCurrentFullStaff").Send()
|
||||
server.ServeError(writer, "Error getting staff info from request: "+err.Error(), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
if actionID == "" {
|
||||
if staff.Rank == NoPerms {
|
||||
// no action requested and user is not logged in, have them go to login page
|
||||
actionID = "login"
|
||||
} else {
|
||||
actionID = "dashboard"
|
||||
}
|
||||
}
|
||||
gcutil.LogStr("staff", staff.Username, infoEv, accessEv, errEv)
|
||||
var managePageBuffer bytes.Buffer
|
||||
action := getAction(actionID, staff.Rank)
|
||||
if action == nil {
|
||||
if wantsJSON {
|
||||
serveError(writer, "notfound", actionID, "action not found", wantsJSON)
|
||||
} else {
|
||||
server.ServeNotFound(writer, request)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if staff.Rank < action.Permissions {
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
errEv.
|
||||
Int("rank", staff.Rank).
|
||||
Int("requiredRank", action.Permissions).
|
||||
Msg("Insufficient permissions")
|
||||
serveError(writer, "permission", actionID, "You do not have permission to access this page", wantsJSON || (action.JSONoutput == AlwaysJSON))
|
||||
return
|
||||
}
|
||||
|
||||
var output interface{}
|
||||
if wantsJSON && action.JSONoutput == NoJSON {
|
||||
output = nil
|
||||
err = &ErrStaffAction{
|
||||
ErrorField: "nojson",
|
||||
Action: actionID,
|
||||
Message: "Requested mod page does not have a JSON output option",
|
||||
}
|
||||
} else {
|
||||
defer func() {
|
||||
if a := recover(); a != nil {
|
||||
serveError(writer, "actionerror", actionID, "Internal server error", wantsJSON)
|
||||
errEv.Caller().
|
||||
Str("action", actionID).
|
||||
Interface("recover", a).
|
||||
Bytes("stack", debug.Stack()).
|
||||
Msg("Recovered from panic while calling manage function")
|
||||
}
|
||||
}()
|
||||
if action.Callback == nil {
|
||||
output = ""
|
||||
err = fmt.Errorf("action %q exists but has no defined callback", action.ID)
|
||||
} else {
|
||||
output, err = action.Callback(writer, request, staff, wantsJSON, infoEv, errEv)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
serveError(writer, "actionerror", actionID, err.Error(), wantsJSON || (action.JSONoutput == AlwaysJSON))
|
||||
return
|
||||
}
|
||||
if action.JSONoutput == AlwaysJSON || (action.JSONoutput > NoJSON && wantsJSON) {
|
||||
writer.Header().Add("Content-Type", "application/json")
|
||||
writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
|
||||
outputJSON, err := gcutil.MarshalJSON(output, true)
|
||||
if err != nil {
|
||||
serveError(writer, "error", actionID, err.Error(), true)
|
||||
gcutil.LogError(err).
|
||||
Str("action", actionID).Send()
|
||||
gcutil.LogStr("action", action.ID, infoEv, accessEv, errEv)
|
||||
if err = req.Request.ParseForm(); err != nil {
|
||||
errEv.Err(err).Caller().Msg("Error parsing form data")
|
||||
server.ServeError(writer, "Error parsing form data: "+err.Error(), wantsJSON, map[string]any{
|
||||
"action": action.ID,
|
||||
})
|
||||
return
|
||||
}
|
||||
serverutil.MinifyWriter(writer, []byte(outputJSON), "application/json")
|
||||
return
|
||||
}
|
||||
|
||||
headerMap := map[string]interface{}{
|
||||
"page_type": "manage",
|
||||
var staff *gcsql.Staff
|
||||
staff, err = GetStaffFromRequest(request)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to get staff from request")
|
||||
server.ServeError(writer, "Error getting staff info from request", wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
gcutil.LogStr("staff", staff.Username, infoEv, accessEv, errEv)
|
||||
|
||||
actionCB := action.Callback
|
||||
if staff.Username == "" && action.Permissions > NoPerms {
|
||||
// action with permissions requested and user is not logged in, have them go to login page
|
||||
actionCB = loginCallback
|
||||
}
|
||||
if staff.Rank < action.Permissions {
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
gcutil.LogWarning().
|
||||
Str("ip", gcutil.GetRealIP(request)).
|
||||
Str("userAgent", request.UserAgent()).
|
||||
Int("rank", staff.Rank).
|
||||
Int("requiredRank", action.Permissions).
|
||||
Msg("Staff requested page with insufficient permissions")
|
||||
serveError(writer, "permission", action.ID, "You do not have permission to access this page", wantsJSON || (action.JSONoutput == AlwaysJSON))
|
||||
return
|
||||
}
|
||||
|
||||
var output any
|
||||
if wantsJSON && action.JSONoutput == NoJSON {
|
||||
output = nil
|
||||
err = &ErrStaffAction{
|
||||
ErrorField: "nojson",
|
||||
Action: action.ID,
|
||||
Message: "Requested mod page does not have a JSON output option",
|
||||
}
|
||||
} else {
|
||||
defer func() {
|
||||
if a := recover(); a != nil {
|
||||
serveError(writer, "actionerror", action.ID, "Internal server error", wantsJSON)
|
||||
gcutil.LogError(nil).
|
||||
Str("ip", gcutil.GetRealIP(request)).
|
||||
Str("userAgent", req.UserAgent()).
|
||||
Interface("recover", a).
|
||||
Bytes("stack", debug.Stack()).
|
||||
Msg("Recovered from panic while calling manage function")
|
||||
}
|
||||
}()
|
||||
if actionCB == nil {
|
||||
output = ""
|
||||
err = fmt.Errorf("action %q exists but has no defined callback", action.ID)
|
||||
} else {
|
||||
output, err = actionCB(writer,
|
||||
request.WithContext(context.WithValue(request.Context(), "actionParams", req.Params())),
|
||||
staff, wantsJSON, infoEv, errEv)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
serveError(writer, "actionerror", action.ID, err.Error(), wantsJSON || (action.JSONoutput == AlwaysJSON))
|
||||
return
|
||||
}
|
||||
if action.JSONoutput == AlwaysJSON || (action.JSONoutput > NoJSON && wantsJSON) {
|
||||
writer.Header().Add("Content-Type", "application/json")
|
||||
writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
|
||||
var outputJSON string
|
||||
if outputJSON, err = gcutil.MarshalJSON(output, true); err != nil {
|
||||
serveError(writer, "error", action.ID, err.Error(), true)
|
||||
errEv.Err(err).Caller().Send()
|
||||
return
|
||||
}
|
||||
serverutil.MinifyWriter(writer, []byte(outputJSON), "application/json")
|
||||
return
|
||||
}
|
||||
|
||||
headerMap := map[string]any{
|
||||
"page_type": "manage",
|
||||
}
|
||||
if action.ID != "dashboard" && action.ID != "login" && action.ID != "logout" {
|
||||
headerMap["includeDashboardLink"] = true
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err = building.BuildPageHeader(&buf, action.Title, "", headerMap); err != nil {
|
||||
gcutil.LogError(err).
|
||||
Str("action", action.ID).
|
||||
Str("staff", "pageHeader").Send()
|
||||
serveError(writer, "error", action.ID, "Failed writing page header: "+err.Error(), false)
|
||||
return
|
||||
}
|
||||
buf.WriteString("<br />" + fmt.Sprint(output) + "<br /><br />")
|
||||
if err = building.BuildPageFooter(&buf); err != nil {
|
||||
gcutil.LogError(err).
|
||||
Str("action", action.ID).
|
||||
Str("staff", "pageFooter").Send()
|
||||
serveError(writer, "error", action.ID, "Failed writing page footer: "+err.Error(), false)
|
||||
return
|
||||
}
|
||||
writer.Write(buf.Bytes())
|
||||
|
||||
return nil
|
||||
}
|
||||
if action.ID != "dashboard" && action.ID != "login" && action.ID != "logout" {
|
||||
headerMap["includeDashboardLink"] = true
|
||||
}
|
||||
if err = building.BuildPageHeader(&managePageBuffer, action.Title, "", headerMap); err != nil {
|
||||
gcutil.LogError(err).
|
||||
Str("action", actionID).
|
||||
Str("staff", "pageHeader").Send()
|
||||
serveError(writer, "error", actionID, "Failed writing page header: "+err.Error(), false)
|
||||
return
|
||||
}
|
||||
managePageBuffer.WriteString("<br />" + fmt.Sprint(output) + "<br /><br />")
|
||||
if err = building.BuildPageFooter(&managePageBuffer); err != nil {
|
||||
gcutil.LogError(err).
|
||||
Str("action", actionID).
|
||||
Str("staff", "pageFooter").Send()
|
||||
serveError(writer, "error", actionID, "Failed writing page footer: "+err.Error(), false)
|
||||
return
|
||||
}
|
||||
writer.Write(managePageBuffer.Bytes())
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/gochan-org/gochan/pkg/gctemplates"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
"github.com/gochan-org/gochan/pkg/server"
|
||||
"github.com/gochan-org/gochan/pkg/server/serverutil"
|
||||
"github.com/rs/zerolog"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
@ -25,6 +26,12 @@ var (
|
|||
ErrBadCredentials = errors.New("invalid username or password")
|
||||
ErrUnableToCreateSession = errors.New("unable to create login session")
|
||||
ErrInvalidSession = errors.New("invalid staff session")
|
||||
dashboardAction = Action{
|
||||
ID: "dashboard",
|
||||
Title: "Dashboard",
|
||||
Permissions: JanitorPerms,
|
||||
Callback: dashboardCallback,
|
||||
}
|
||||
)
|
||||
|
||||
func createSession(key, username, password string, request *http.Request, writer http.ResponseWriter) error {
|
||||
|
@ -88,7 +95,7 @@ func createSession(key, username, password string, request *http.Request, writer
|
|||
return nil
|
||||
}
|
||||
|
||||
func getCurrentStaff(request *http.Request) (string, error) { //TODO after refactor, check if still used
|
||||
func getCurrentStaff(request *http.Request) (string, error) {
|
||||
staff, err := GetStaffFromRequest(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -104,10 +111,12 @@ func GetStaffFromRequest(request *http.Request) (*gcsql.Staff, error) {
|
|||
return &gcsql.Staff{Rank: 0}, nil
|
||||
}
|
||||
staff, err := gcsql.GetStaffBySession(sessionCookie.Value)
|
||||
if err != nil {
|
||||
staff = &gcsql.Staff{Rank: 0}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return &gcsql.Staff{Rank: 0}, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return staff, err
|
||||
return staff, nil
|
||||
}
|
||||
|
||||
// GetStaffRank returns the rank number of the staff referenced in the request
|
||||
|
@ -119,9 +128,10 @@ func GetStaffRank(request *http.Request) int {
|
|||
return staff.Rank
|
||||
}
|
||||
|
||||
func init() {
|
||||
func InitManagePages() {
|
||||
RegisterManagePage("actions", "Staff actions", JanitorPerms, AlwaysJSON, getStaffActions)
|
||||
RegisterManagePage("dashboard", "Dashboard", JanitorPerms, NoJSON, dashboardCallback)
|
||||
RegisterManagePage(dashboardAction.ID, dashboardAction.Title, dashboardAction.Permissions, dashboardAction.JSONoutput, dashboardAction.Callback)
|
||||
server.GetRouter().GET(config.WebPath("/manage"), setupManageFunction(&dashboardAction))
|
||||
registerNoPermPages()
|
||||
registerJanitorPages()
|
||||
registerModeratorPages()
|
||||
|
|
15
templates/manage_filter_hits.html
Normal file
15
templates/manage_filter_hits.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<a href="{{webPath `/manage/filters/`}}">Back to filter list</a><br/>
|
||||
{{with .hits}}
|
||||
<table class="mgmt-table filterlist">
|
||||
<tr><th>Filter ID</th><th>Match time</th><th>Post data</th></tr>
|
||||
{{range $_, $hit := .}}
|
||||
<tr>
|
||||
<td><a href="{{webPath `/manage/filters`}}?edit={{$hit.FilterID}}">{{$hit.FilterID}}</a></td>
|
||||
<td>{{formatTimestamp $hit.MatchTime}}</td>
|
||||
<td><textarea rows="12" cols="80">{{$hit.PostData}}</textarea></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
{{else}}
|
||||
<i>No hits</i>
|
||||
{{end}}
|
|
@ -110,7 +110,7 @@ For wordfilters go <a href="{{webPath `/manage/wordfilters`}}">here</a>.
|
|||
</form>
|
||||
<table class="mgmt-table filterlist">
|
||||
<colgroup>
|
||||
<col class="row-actions">
|
||||
<col class="filter-actions">
|
||||
<col class="filter-action">
|
||||
<col class="filter-conditions">
|
||||
<col class="staff-boards">
|
||||
|
@ -125,7 +125,11 @@ For wordfilters go <a href="{{webPath `/manage/wordfilters`}}">here</a>.
|
|||
{{- range $f, $filter := $.filters -}}
|
||||
{{$boardsCell := index $.filterTableBoards $f}}
|
||||
<tr>
|
||||
<td><a href="{{webPath `/manage/filters`}}?edit={{$filter.ID}}">Edit</a> | <a href="{{webPath `/manage/filters`}}?{{if $filter.IsActive}}disable{{else}}enable{{end}}={{$filter.ID}}">{{if $filter.IsActive}}Disable{{else}}Enable{{end}}</a></td>
|
||||
<td class="filter-actions">
|
||||
<a href="{{webPath `/manage/filters`}}?edit={{$filter.ID}}">Edit</a> |
|
||||
<a href="{{webPath `/manage/filters`}}?{{if $filter.IsActive}}disable{{else}}enable{{end}}={{$filter.ID}}">{{if $filter.IsActive}}Disable{{else}}Enable{{end}}</a> |
|
||||
<a href="{{webPathDir `/manage/filters/hits/`}}{{$filter.ID}}">Hits</a>
|
||||
</td>
|
||||
<td>{{index $.actions $filter.MatchAction}}</td>
|
||||
<td>{{index $.conditions $f}}</td>
|
||||
<td>{{if eq $boardsCell ""}}<i>all boards</i>{{else}}{{$boardsCell}}{{end}}</td>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue