Added support for modlogs.

Added support for more mod-action posts in topics.

Fixed an easter egg.
Fixed a few redirects.
Fixed an issue with users not being loaded if they weren't already in the cache in the User Editor.
This commit is contained in:
Azareal 2017-04-06 18:37:32 +01:00
parent 6d17e08605
commit a906f17470
10 changed files with 253 additions and 55 deletions

View File

@ -174,7 +174,8 @@ CREATE TABLE `moderation_logs`(
`elementID` int not null, `elementID` int not null,
`elementType` varchar(100) not null, `elementType` varchar(100) not null,
`ipaddress` varchar(200) not null, `ipaddress` varchar(200) not null,
`actorID` int not null `actorID` int not null,
`doneAt` datetime not null
); );
CREATE TABLE `administration_logs`( CREATE TABLE `administration_logs`(
@ -182,7 +183,8 @@ CREATE TABLE `administration_logs`(
`elementID` int not null, `elementID` int not null,
`elementType` varchar(100) not null, `elementType` varchar(100) not null,
`ipaddress` varchar(200) not null, `ipaddress` varchar(200) not null,
`actorID` int not null `actorID` int not null,
`doneAt` datetime not null
); );
INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool'); INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool');

BIN
images/modlogs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -264,6 +264,7 @@ func main(){
router.HandleFunc("/panel/groups/edit/submit/", route_panel_groups_edit_submit) router.HandleFunc("/panel/groups/edit/submit/", route_panel_groups_edit_submit)
router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit) router.HandleFunc("/panel/groups/edit/perms/submit/", route_panel_groups_edit_perms_submit)
router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit) router.HandleFunc("/panel/groups/create/", route_panel_groups_create_submit)
router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod)
router.HandleFunc("/api/", route_api) router.HandleFunc("/api/", route_api)
//router.HandleFunc("/exit/", route_exit) //router.HandleFunc("/exit/", route_exit)

View File

@ -209,6 +209,23 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request) {
return return
} }
//topic.Sticky = true //topic.Sticky = true
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return
}
err = addModLog("stick",tid,"topic",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
_, err = create_action_reply_stmt.Exec(tid,"stick",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = topics.Load(tid) err = topics.Load(tid)
if err != nil { if err != nil {
LocalError("This topic doesn't exist!",w,r,user) LocalError("This topic doesn't exist!",w,r,user)
@ -248,6 +265,23 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request) {
return return
} }
//topic.Sticky = false //topic.Sticky = false
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return
}
err = addModLog("unstick",tid,"topic",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
_, err = create_action_reply_stmt.Exec(tid,"unstick",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = topics.Load(tid) err = topics.Load(tid)
if err != nil { if err != nil {
LocalError("This topic doesn't exist!",w,r,user) LocalError("This topic doesn't exist!",w,r,user)
@ -385,6 +419,17 @@ func route_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
InternalErrorJSQ(err,w,r,is_js) InternalErrorJSQ(err,w,r,is_js)
} }
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return
}
err = addModLog("delete",tid,"reply",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = topics.Load(tid) err = topics.Load(tid)
if err != nil { if err != nil {
LocalError("This topic no longer exists!",w,r,user) LocalError("This topic no longer exists!",w,r,user)
@ -544,6 +589,10 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) {
LocalError("The provided User ID is not a valid number.",w,r,user) LocalError("The provided User ID is not a valid number.",w,r,user)
return return
} }
if uid == -2 {
LocalError("Sigh, are you really trying to ban me? Do you despise so much? Despite all of our adventures over at /arcane-tower/...?",w,r,user)
return
}
var group int var group int
var is_super_admin bool var is_super_admin bool
@ -561,11 +610,7 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) {
return return
} }
if uid == user.ID { if uid == user.ID {
LocalError("You may not ban yourself.",w,r,user) LocalError("Why are you trying to ban yourself? Stop that.",w,r,user)
return
}
if uid == -2 {
LocalError("You may not ban me. Fine, I will offer up some guidance unto thee. Come to my lair, young one. /arcane-tower/",w,r,user)
return return
} }
@ -580,12 +625,23 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) {
return return
} }
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return
}
err = addModLog("ban",uid,"user",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = users.Load(uid) err = users.Load(uid)
if err != nil { if err != nil {
LocalError("This user no longer exists!",w,r,user) LocalError("This user no longer exists!",w,r,user)
return return
} }
http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther) http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
} }
func route_unban(w http.ResponseWriter, r *http.Request) { func route_unban(w http.ResponseWriter, r *http.Request) {
@ -630,12 +686,23 @@ func route_unban(w http.ResponseWriter, r *http.Request) {
return return
} }
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return
}
err = addModLog("unban",uid,"user",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = users.Load(uid) err = users.Load(uid)
if err != nil { if err != nil {
LocalError("This user no longer exists!",w,r,user) LocalError("This user no longer exists!",w,r,user)
return return
} }
http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther) http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
} }
func route_activate(w http.ResponseWriter, r *http.Request) { func route_activate(w http.ResponseWriter, r *http.Request) {
@ -685,10 +752,21 @@ func route_activate(w http.ResponseWriter, r *http.Request) {
return return
} }
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
LocalError("Bad IP",w,r,user)
return
}
err = addModLog("activate",uid,"user",ipaddress,user.ID)
if err != nil {
InternalError(err,w,r)
return
}
err = users.Load(uid) err = users.Load(uid)
if err != nil { if err != nil {
LocalError("This user no longer exists!",w,r,user) LocalError("This user no longer exists!",w,r,user)
return return
} }
http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther) http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
} }

View File

@ -597,13 +597,13 @@ func init_database(err error) {
} }
log.Print("Preparing add_modlog_entry statement.") log.Print("Preparing add_modlog_entry statement.")
add_modlog_entry_stmt, err = db.Prepare("INSERT INTO moderation_logs(action,elementID,elementType,ipaddress,actorID) VALUES(?,?,?,?,?)") add_modlog_entry_stmt, err = db.Prepare("INSERT INTO moderation_logs(action,elementID,elementType,ipaddress,actorID,doneAt) VALUES(?,?,?,?,?,NOW())")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Print("Preparing add_adminlog_entry statement.") log.Print("Preparing add_adminlog_entry statement.")
add_adminlog_entry_stmt, err = db.Prepare("INSERT INTO moderation_logs(action,elementID,elementType,actorID) VALUES(?,?,?,?)") add_adminlog_entry_stmt, err = db.Prepare("INSERT INTO moderation_logs(action,elementID,elementType,ipaddress,actorID,doneAt) VALUES(?,?,?,?,?,NOW())")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -4,6 +4,7 @@ import "bytes"
import "strings" import "strings"
import "strconv" import "strconv"
import "regexp" import "regexp"
import "html/template"
type Page struct type Page struct
{ {
@ -124,6 +125,21 @@ type EditGroupPermsPage struct
ExtData interface{} ExtData interface{}
} }
type Log struct {
Action template.HTML
IPAddress string
DoneAt string
}
type LogsPage struct
{
Title string
CurrentUser User
NoticeList []string
Logs []Log
ExtData interface{}
}
type PageSimple struct type PageSimple struct
{ {
Title string Title string

View File

@ -7,6 +7,7 @@ import "strconv"
import "html" import "html"
import "encoding/json" import "encoding/json"
import "net/http" import "net/http"
import "html/template"
import "database/sql" import "database/sql"
import _ "github.com/go-sql-driver/mysql" import _ "github.com/go-sql-driver/mysql"
@ -571,14 +572,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request){
return return
} }
puser.Is_Admin = puser.Is_Super_Admin || groups[puser.Group].Is_Admin init_user_perms(&puser)
puser.Is_Super_Mod = puser.Is_Admin || groups[puser.Group].Is_Mod
puser.Is_Mod = puser.Is_Super_Mod
puser.Is_Banned = groups[puser.Group].Is_Banned
if puser.Is_Banned && puser.Is_Super_Mod {
puser.Is_Banned = false
}
if puser.Avatar != "" { if puser.Avatar != "" {
if puser.Avatar[0] == '.' { if puser.Avatar[0] == '.' {
puser.Avatar = "/uploads/avatar_" + strconv.Itoa(puser.ID) + puser.Avatar puser.Avatar = "/uploads/avatar_" + strconv.Itoa(puser.ID) + puser.Avatar
@ -625,7 +619,7 @@ func route_panel_users_edit(w http.ResponseWriter, r *http.Request){
return return
} }
targetUser, err := users.Get(uid) targetUser, err := users.CascadeGet(uid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
LocalError("The user you're trying to edit doesn't exist.",w,r,user) LocalError("The user you're trying to edit doesn't exist.",w,r,user)
return return
@ -677,7 +671,7 @@ func route_panel_users_edit_submit(w http.ResponseWriter, r *http.Request){
return return
} }
targetUser, err := users.Get(tid) targetUser, err := users.CascadeGet(tid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
LocalError("The user you're trying to edit doesn't exist.",w,r,user) LocalError("The user you're trying to edit doesn't exist.",w,r,user)
return return
@ -786,11 +780,7 @@ func route_panel_groups(w http.ResponseWriter, r *http.Request){
rank_emoji = "👪" rank_emoji = "👪"
} }
if user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod) { can_edit = user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod)
can_edit = true
} else {
can_edit = false
}
groupList = append(groupList, GroupAdmin{group.ID,group.Name,rank,rank_emoji,can_edit,can_delete}) groupList = append(groupList, GroupAdmin{group.ID,group.Name,rank,rank_emoji,can_edit,can_delete})
} }
@ -845,10 +835,7 @@ func route_panel_groups_edit(w http.ResponseWriter, r *http.Request){
rank = "Member" rank = "Member"
} }
var disable_rank bool disable_rank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6)
if !user.Perms.EditGroupGlobalPerms || (group.ID == 6) {
disable_rank = true
}
pi := EditGroupPage{"Group Editor",user,noticeList,group.ID,group.Name,group.Tag,rank,disable_rank,nil} pi := EditGroupPage{"Group Editor",user,noticeList,group.ID,group.Name,group.Tag,rank,disable_rank,nil}
err = templates.ExecuteTemplate(w,"panel-group-edit.html",pi) err = templates.ExecuteTemplate(w,"panel-group-edit.html",pi)
@ -1305,3 +1292,106 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request){
http.Redirect(w,r,"/panel/themes/",http.StatusSeeOther) http.Redirect(w,r,"/panel/themes/",http.StatusSeeOther)
} }
func route_panel_logs_mod(w http.ResponseWriter, r *http.Request){
user, noticeList, ok := SessionCheck(w,r)
if !ok {
return
}
if !user.Is_Super_Mod || !user.Perms.ManageThemes {
NoPermissions(w,r,user)
return
}
rows, err := db.Query("select action, elementID, elementType, ipaddress, actorID, doneAt from moderation_logs")
if err != nil {
InternalError(err,w,r)
return
}
defer rows.Close()
var logs []Log
var action, elementType, ipaddress, doneAt string
var elementID, actorID int
for rows.Next() {
err := rows.Scan(&action,&elementID,&elementType, &ipaddress, &actorID, &doneAt)
if err != nil {
InternalError(err,w,r)
return
}
actor, err := users.CascadeGet(actorID)
if err != nil {
actor = &User{Name:"Unknown"}
}
switch(action) {
case "lock":
topic, err := topics.CascadeGet(elementID)
if err != nil {
topic = &Topic{Title:"Unknown"}
}
action = "<a href='" + build_topic_url(elementID) + "'>" + topic.Title + "</a> was locked by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
case "unlock":
topic, err := topics.CascadeGet(elementID)
if err != nil {
topic = &Topic{Title:"Unknown"}
}
action = "<a href='" + build_topic_url(elementID) + "'>" + topic.Title + "</a> was reopened by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
case "stick":
topic, err := topics.CascadeGet(elementID)
if err != nil {
topic = &Topic{Title:"Unknown"}
}
action = "<a href='" + build_topic_url(elementID) + "'>" + topic.Title + "</a> was pinned by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
case "unstick":
topic, err := topics.CascadeGet(elementID)
if err != nil {
topic = &Topic{Title:"Unknown"}
}
action = "<a href='" + build_topic_url(elementID) + "'>" + topic.Title + "</a> was unpinned by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
case "delete":
if elementType == "topic" {
action = "Topic #" + strconv.Itoa(elementID) + " was deleted by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
} else {
topic, err := get_topic_by_reply(elementID)
if err != nil {
topic = &Topic{Title:"Unknown"}
}
action = "A reply in <a href='" + build_topic_url(elementID) + "'>" + topic.Title + "</a> was deleted by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
}
case "ban":
targetUser, err := users.CascadeGet(elementID)
if err != nil {
targetUser = &User{Name:"Unknown"}
}
action = "<a href='" + build_profile_url(elementID) + "'>" + targetUser.Name + "</a> was banned by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
case "unban":
targetUser, err := users.CascadeGet(elementID)
if err != nil {
targetUser = &User{Name:"Unknown"}
}
action = "<a href='" + build_profile_url(elementID) + "'>" + targetUser.Name + "</a> was unbanned by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
case "activate":
targetUser, err := users.CascadeGet(elementID)
if err != nil {
targetUser = &User{Name:"Unknown"}
}
action = "<a href='" + build_profile_url(elementID) + "'>" + targetUser.Name + "</a> was activated by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
default:
action = "Unknown action '" + action + "' by <a href='" + build_profile_url(actorID) + "'>"+actor.Name+"</a>"
}
logs = append(logs, Log{Action:template.HTML(action),IPAddress:ipaddress,DoneAt:doneAt})
}
err = rows.Err()
if err != nil {
InternalError(err,w,r)
return
}
pi := LogsPage{"Moderation Logs",user,noticeList,logs,nil}
err = templates.ExecuteTemplate(w,"panel-modlogs.html",pi)
if err != nil {
log.Print(err)
}
}

View File

@ -288,12 +288,9 @@ func route_forums(w http.ResponseWriter, r *http.Request){
} }
func route_topic_id(w http.ResponseWriter, r *http.Request){ func route_topic_id(w http.ResponseWriter, r *http.Request){
var( var err error
err error var page, offset int
page int var replyList []Reply
offset int
replyList []Reply
)
page, _ = strconv.Atoi(r.FormValue("page")) page, _ = strconv.Atoi(r.FormValue("page"))
tid, err := strconv.Atoi(r.URL.Path[len("/topic/"):]) tid, err := strconv.Atoi(r.URL.Path[len("/topic/"):])
@ -416,6 +413,12 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
case "unlock": case "unlock":
replyItem.ActionType = "This topic has been reopened by <a href='" + build_profile_url(replyItem.CreatedBy) + "'>" + replyItem.CreatedByName + "</a>" replyItem.ActionType = "This topic has been reopened by <a href='" + build_profile_url(replyItem.CreatedBy) + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F513;&#xFE0E" replyItem.ActionIcon = "&#x1F513;&#xFE0E"
case "stick":
replyItem.ActionType = "This topic has been pinned by <a href='" + build_profile_url(replyItem.CreatedBy) + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F4CC;&#xFE0E"
case "unstick":
replyItem.ActionType = "This topic has been unpinned by <a href='" + build_profile_url(replyItem.CreatedBy) + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F4CC;&#xFE0E"
default: default:
replyItem.ActionType = replyItem.ActionType + " has happened" replyItem.ActionType = replyItem.ActionType + " has happened"
replyItem.ActionIcon = "" replyItem.ActionIcon = ""
@ -452,23 +455,11 @@ func route_profile(w http.ResponseWriter, r *http.Request){
return return
} }
var( var err error
err error var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag string
rid int var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
replyContent string var replyCss template.CSS
replyCreatedBy int var replyList []Reply
replyCreatedByName string
replyCreatedAt string
replyLastEdit int
replyLastEditBy int
replyAvatar string
replyCss template.CSS
replyLines int
replyTag string
replyGroup int
replyList []Reply
)
pid, err := strconv.Atoi(r.URL.Path[len("/user/"):]) pid, err := strconv.Atoi(r.URL.Path[len("/user/"):])
if err != nil { if err != nil {

View File

@ -9,4 +9,5 @@
{{if .CurrentUser.Perms.ManageThemes}}<div class="rowitem passive"><a href="/panel/themes/">Themes</a></div>{{end}} {{if .CurrentUser.Perms.ManageThemes}}<div class="rowitem passive"><a href="/panel/themes/">Themes</a></div>{{end}}
{{if .CurrentUser.Perms.ManagePlugins}}<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>{{end}} {{if .CurrentUser.Perms.ManagePlugins}}<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>{{end}}
<div class="rowitem passive"><a href="/forum/1">Reports</a></div> <div class="rowitem passive"><a href="/forum/1">Reports</a></div>
<div class="rowitem passive"><a href="/panel/logs/mod/">Logs</a></div>
</div> </div>

View File

@ -0,0 +1,19 @@
{{template "header.html" . }}
{{template "panel-menu.html" . }}
<div class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem rowhead"><a>Moderation Logs</a></div>
</div>
<div class="colstack_item">
{{range .Logs}}
<div class="rowitem" style="font-weight: normal;text-transform: none;">
<a style="font-size: 17px;">{{.Action}}</a><br />
<small style="margin-left: 2px;">IP: {{.IPAddress}}</small>
<span style="float: right;">
<span style="font-size: 16px;position:relative;top: -2px;">{{.DoneAt}}</span>
</span>
</div>
{{end}}
</div>
</div>
{{template "footer.html" . }}