diff --git a/data.sql b/data.sql index 7a924f8d..452b4c89 100644 --- a/data.sql +++ b/data.sql @@ -174,7 +174,8 @@ CREATE TABLE `moderation_logs`( `elementID` int not null, `elementType` varchar(100) not null, `ipaddress` varchar(200) not null, - `actorID` int not null + `actorID` int not null, + `doneAt` datetime not null ); CREATE TABLE `administration_logs`( @@ -182,7 +183,8 @@ CREATE TABLE `administration_logs`( `elementID` int not null, `elementType` varchar(100) 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'); diff --git a/images/modlogs.png b/images/modlogs.png new file mode 100644 index 00000000..dba1b374 Binary files /dev/null and b/images/modlogs.png differ diff --git a/main.go b/main.go index e1b1840a..d8c9ee87 100644 --- a/main.go +++ b/main.go @@ -264,6 +264,7 @@ func main(){ 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/create/", route_panel_groups_create_submit) + router.HandleFunc("/panel/logs/mod/", route_panel_logs_mod) router.HandleFunc("/api/", route_api) //router.HandleFunc("/exit/", route_exit) diff --git a/mod_routes.go b/mod_routes.go index 951c129d..27c31799 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -209,6 +209,23 @@ func route_stick_topic(w http.ResponseWriter, r *http.Request) { return } //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) if err != nil { LocalError("This topic doesn't exist!",w,r,user) @@ -248,6 +265,23 @@ func route_unstick_topic(w http.ResponseWriter, r *http.Request) { return } //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) if err != nil { 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) } + 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) if err != nil { LocalError("This topic no longer exists!",w,r,user) @@ -435,7 +480,7 @@ func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request) { } if is_js == "0" { - http.Redirect(w,r, "/user/" + strconv.Itoa(uid) + "#reply-" + strconv.Itoa(rid), http.StatusSeeOther) + http.Redirect(w,r,"/user/" + strconv.Itoa(uid) + "#reply-" + strconv.Itoa(rid), http.StatusSeeOther) } else { fmt.Fprintf(w,`{"success":"1"}`) } @@ -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) 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 is_super_admin bool @@ -561,11 +610,7 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) { return } if uid == user.ID { - LocalError("You may not ban yourself.",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) + LocalError("Why are you trying to ban yourself? Stop that.",w,r,user) return } @@ -580,12 +625,23 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request) { 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) if err != nil { LocalError("This user no longer exists!",w,r,user) 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) { @@ -630,12 +686,23 @@ func route_unban(w http.ResponseWriter, r *http.Request) { 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) if err != nil { LocalError("This user no longer exists!",w,r,user) 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) { @@ -685,10 +752,21 @@ func route_activate(w http.ResponseWriter, r *http.Request) { 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) if err != nil { LocalError("This user no longer exists!",w,r,user) return } - http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther) + http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther) } diff --git a/mysql.go b/mysql.go index 99dbfffa..d571e987 100644 --- a/mysql.go +++ b/mysql.go @@ -597,13 +597,13 @@ func init_database(err error) { } 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 { log.Fatal(err) } 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 { log.Fatal(err) } diff --git a/pages.go b/pages.go index 47c3813a..96a4b546 100644 --- a/pages.go +++ b/pages.go @@ -4,6 +4,7 @@ import "bytes" import "strings" import "strconv" import "regexp" +import "html/template" type Page struct { @@ -124,6 +125,21 @@ type EditGroupPermsPage struct 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 { Title string diff --git a/panel_routes.go b/panel_routes.go index a0ce15a0..aef8324c 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -7,6 +7,7 @@ import "strconv" import "html" import "encoding/json" import "net/http" +import "html/template" import "database/sql" import _ "github.com/go-sql-driver/mysql" @@ -571,14 +572,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request){ return } - puser.Is_Admin = puser.Is_Super_Admin || groups[puser.Group].Is_Admin - 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 - } - + init_user_perms(&puser) if puser.Avatar != "" { if puser.Avatar[0] == '.' { 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 } - targetUser, err := users.Get(uid) + targetUser, err := users.CascadeGet(uid) if err == sql.ErrNoRows { LocalError("The user you're trying to edit doesn't exist.",w,r,user) return @@ -677,7 +671,7 @@ func route_panel_users_edit_submit(w http.ResponseWriter, r *http.Request){ return } - targetUser, err := users.Get(tid) + targetUser, err := users.CascadeGet(tid) if err == sql.ErrNoRows { LocalError("The user you're trying to edit doesn't exist.",w,r,user) return @@ -786,11 +780,7 @@ func route_panel_groups(w http.ResponseWriter, r *http.Request){ rank_emoji = "👪" } - if user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod) { - can_edit = true - } else { - can_edit = false - } + can_edit = user.Perms.EditGroup && (!group.Is_Admin || user.Perms.EditGroupAdmin) && (!group.Is_Mod || user.Perms.EditGroupSuperMod) 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" } - var disable_rank bool - if !user.Perms.EditGroupGlobalPerms || (group.ID == 6) { - disable_rank = true - } + disable_rank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6) 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) @@ -1305,3 +1292,106 @@ func route_panel_themes_default(w http.ResponseWriter, r *http.Request){ 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 = "" + topic.Title + " was locked by "+actor.Name+"" + case "unlock": + topic, err := topics.CascadeGet(elementID) + if err != nil { + topic = &Topic{Title:"Unknown"} + } + action = "" + topic.Title + " was reopened by "+actor.Name+"" + case "stick": + topic, err := topics.CascadeGet(elementID) + if err != nil { + topic = &Topic{Title:"Unknown"} + } + action = "" + topic.Title + " was pinned by "+actor.Name+"" + case "unstick": + topic, err := topics.CascadeGet(elementID) + if err != nil { + topic = &Topic{Title:"Unknown"} + } + action = "" + topic.Title + " was unpinned by "+actor.Name+"" + case "delete": + if elementType == "topic" { + action = "Topic #" + strconv.Itoa(elementID) + " was deleted by "+actor.Name+"" + } else { + topic, err := get_topic_by_reply(elementID) + if err != nil { + topic = &Topic{Title:"Unknown"} + } + action = "A reply in " + topic.Title + " was deleted by "+actor.Name+"" + } + case "ban": + targetUser, err := users.CascadeGet(elementID) + if err != nil { + targetUser = &User{Name:"Unknown"} + } + action = "" + targetUser.Name + " was banned by "+actor.Name+"" + case "unban": + targetUser, err := users.CascadeGet(elementID) + if err != nil { + targetUser = &User{Name:"Unknown"} + } + action = "" + targetUser.Name + " was unbanned by "+actor.Name+"" + case "activate": + targetUser, err := users.CascadeGet(elementID) + if err != nil { + targetUser = &User{Name:"Unknown"} + } + action = "" + targetUser.Name + " was activated by "+actor.Name+"" + default: + action = "Unknown action '" + action + "' by "+actor.Name+"" + } + 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) + } +} diff --git a/routes.go b/routes.go index f6af4b4b..5ea29e6e 100644 --- a/routes.go +++ b/routes.go @@ -288,12 +288,9 @@ func route_forums(w http.ResponseWriter, r *http.Request){ } func route_topic_id(w http.ResponseWriter, r *http.Request){ - var( - err error - page int - offset int - replyList []Reply - ) + var err error + var page, offset int + var replyList []Reply page, _ = strconv.Atoi(r.FormValue("page")) 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": replyItem.ActionType = "This topic has been reopened by " + replyItem.CreatedByName + "" replyItem.ActionIcon = "🔓︎" + case "stick": + replyItem.ActionType = "This topic has been pinned by " + replyItem.CreatedByName + "" + replyItem.ActionIcon = "📌︎" + case "unstick": + replyItem.ActionType = "This topic has been unpinned by " + replyItem.CreatedByName + "" + replyItem.ActionIcon = "📌︎" default: replyItem.ActionType = replyItem.ActionType + " has happened" replyItem.ActionIcon = "" @@ -452,23 +455,11 @@ func route_profile(w http.ResponseWriter, r *http.Request){ return } - var( - err error - rid int - replyContent string - replyCreatedBy int - replyCreatedByName string - replyCreatedAt string - replyLastEdit int - replyLastEditBy int - replyAvatar string - replyCss template.CSS - replyLines int - replyTag string - replyGroup int - - replyList []Reply - ) + var err error + var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag string + var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int + var replyCss template.CSS + var replyList []Reply pid, err := strconv.Atoi(r.URL.Path[len("/user/"):]) if err != nil { diff --git a/templates/panel-inner-menu.html b/templates/panel-inner-menu.html index 50264dfc..6c27e5bf 100644 --- a/templates/panel-inner-menu.html +++ b/templates/panel-inner-menu.html @@ -9,4 +9,5 @@ {{if .CurrentUser.Perms.ManageThemes}}
Themes
{{end}} {{if .CurrentUser.Perms.ManagePlugins}}
Plugins
{{end}}
Reports
+
Logs
\ No newline at end of file diff --git a/templates/panel-modlogs.html b/templates/panel-modlogs.html new file mode 100644 index 00000000..4e556b26 --- /dev/null +++ b/templates/panel-modlogs.html @@ -0,0 +1,19 @@ +{{template "header.html" . }} +{{template "panel-menu.html" . }} +
+
+ +
+
+ {{range .Logs}} +
+ {{.Action}}
+ IP: {{.IPAddress}} + + {{.DoneAt}} + +
+ {{end}} +
+
+{{template "footer.html" . }} \ No newline at end of file