Admin insights block lists page
This commit is contained in:
parent
f618973e80
commit
fc8014913d
|
@ -32,6 +32,7 @@ const (
|
|||
// - Impersonate: ability to log in as a user account
|
||||
// - Ban: ability to ban/unban users
|
||||
// - Delete: ability to delete user accounts
|
||||
ScopeUserInsight = "admin.user.insights"
|
||||
ScopeUserImpersonate = "admin.user.impersonate"
|
||||
ScopeUserBan = "admin.user.ban"
|
||||
ScopeUserPromote = "admin.user.promote"
|
||||
|
@ -62,6 +63,7 @@ func ListAdminScopes() []string {
|
|||
ScopeCertificationView,
|
||||
ScopeForumAdmin,
|
||||
ScopeAdminScopeAdmin,
|
||||
ScopeUserInsight,
|
||||
ScopeUserImpersonate,
|
||||
ScopeUserBan,
|
||||
ScopeUserDelete,
|
||||
|
|
|
@ -46,7 +46,26 @@ func UserActions() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Template variables.
|
||||
var vars = map[string]interface{}{
|
||||
"Intent": intent,
|
||||
"User": user,
|
||||
}
|
||||
|
||||
switch intent {
|
||||
case "insights":
|
||||
// Admin insights (peek at block lists, etc.)
|
||||
if !currentUser.HasAdminScope(config.ScopeUserInsight) {
|
||||
session.FlashError(w, r, "Missing admin scope: %s", config.ScopeUserInsight)
|
||||
templates.Redirect(w, "/admin")
|
||||
return
|
||||
}
|
||||
|
||||
insights, err := models.GetBlocklistInsights(user)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Error getting blocklist insights: %s", err)
|
||||
}
|
||||
vars["BlocklistInsights"] = insights
|
||||
case "impersonate":
|
||||
// Scope check.
|
||||
if !currentUser.HasAdminScope(config.ScopeUserImpersonate) {
|
||||
|
@ -124,10 +143,6 @@ func UserActions() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
var vars = map[string]interface{}{
|
||||
"Intent": intent,
|
||||
"User": user,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
|
@ -183,12 +184,93 @@ func BlockedUsernames(user *User) []string {
|
|||
).Where(
|
||||
"id IN ?", userIDs,
|
||||
).Scan(&usernames); res.Error != nil {
|
||||
log.Error("BlockedUsernames(%d): %s", user.Username, res.Error)
|
||||
log.Error("BlockedUsernames(%s): %s", user.Username, res.Error)
|
||||
}
|
||||
|
||||
return usernames
|
||||
}
|
||||
|
||||
// GetBlocklistInsights returns detailed block lists (both directions) about a user, for admin insight.
|
||||
func GetBlocklistInsights(user *User) (*BlocklistInsight, error) {
|
||||
// Collect ALL user IDs (both directions) of this user's blocklist.
|
||||
var (
|
||||
bs = []*Block{}
|
||||
forward = []*Block{} // Users they block
|
||||
reverse = []*Block{} // Users who block the target
|
||||
userIDs = []uint64{user.ID}
|
||||
usernames = map[uint64]string{}
|
||||
)
|
||||
|
||||
// Get the complete blocklist and bucket them into forward and reverse.
|
||||
DB.Where("source_user_id = ? OR target_user_id = ?", user.ID, user.ID).Order("created_at desc").Find(&bs)
|
||||
for _, row := range bs {
|
||||
if row.SourceUserID == user.ID {
|
||||
forward = append(forward, row)
|
||||
userIDs = append(userIDs, row.TargetUserID)
|
||||
} else {
|
||||
reverse = append(reverse, row)
|
||||
userIDs = append(userIDs, row.SourceUserID)
|
||||
}
|
||||
}
|
||||
|
||||
// Map all the user IDs to user names.
|
||||
if len(userIDs) > 0 {
|
||||
type scanItem struct {
|
||||
ID uint64
|
||||
Username string
|
||||
}
|
||||
var scan = []scanItem{}
|
||||
if res := DB.Table(
|
||||
"users",
|
||||
).Select(
|
||||
"id",
|
||||
"username",
|
||||
).Where(
|
||||
"id IN ?", userIDs,
|
||||
).Scan(&scan); res.Error != nil {
|
||||
return nil, fmt.Errorf("GetBlocklistInsights(%s): mapping user IDs to names: %s", user.Username, res.Error)
|
||||
}
|
||||
|
||||
for _, row := range scan {
|
||||
usernames[row.ID] = row.Username
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble the final result.
|
||||
var result = &BlocklistInsight{
|
||||
Blocks: []BlocklistInsightUser{},
|
||||
BlockedBy: []BlocklistInsightUser{},
|
||||
}
|
||||
for _, row := range forward {
|
||||
if username, ok := usernames[row.TargetUserID]; ok {
|
||||
result.Blocks = append(result.Blocks, BlocklistInsightUser{
|
||||
Username: username,
|
||||
Date: row.CreatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, row := range reverse {
|
||||
if username, ok := usernames[row.SourceUserID]; ok {
|
||||
result.BlockedBy = append(result.BlockedBy, BlocklistInsightUser{
|
||||
Username: username,
|
||||
Date: row.CreatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type BlocklistInsight struct {
|
||||
Blocks []BlocklistInsightUser
|
||||
BlockedBy []BlocklistInsightUser
|
||||
}
|
||||
|
||||
type BlocklistInsightUser struct {
|
||||
Username string
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
// UnblockUser removes targetUserID from your blocklist.
|
||||
func UnblockUser(sourceUserID, targetUserID uint64) error {
|
||||
result := DB.Where(
|
||||
|
|
|
@ -459,6 +459,12 @@
|
|||
|
||||
<div class="card-content">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a href="/admin/user-action?intent=insights&user_id={{.User.ID}}">
|
||||
<span class="icon"><i class="fa fa-search"></i></span>
|
||||
<span>Admin insights <small>(block lists, etc.)</small></span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/user-action?intent=impersonate&user_id={{.User.ID}}">
|
||||
<span class="icon"><i class="fa fa-ghost"></i></span>
|
||||
|
|
|
@ -54,7 +54,53 @@
|
|||
<input type="hidden" name="intent" value="{{.Intent}}">
|
||||
<input type="hidden" name="user_id" value="{{.User.ID}}">
|
||||
|
||||
{{if eq .Intent "impersonate"}}
|
||||
{{if eq .Intent "insights"}}
|
||||
<div class="block content">
|
||||
<h2>Admin Insights</h2>
|
||||
|
||||
<p>
|
||||
This page gives a peek into the database to glean some insights about a user.
|
||||
So far, this means taking a look at their block lists: how many people do they
|
||||
block (and who), and more importantly, how many people are blocking them. It
|
||||
may be useful information to guage a problematic user, if they are angering or
|
||||
creeping a lot of people out and ending up on a lot of block lists.
|
||||
</p>
|
||||
|
||||
<h3>Block Lists</h3>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h5 class="has-text-warning">Forward List <span class="tag is-warning">{{len .BlocklistInsights.Blocks}}</span></h5>
|
||||
|
||||
<p>
|
||||
(Users who {{.User.Username}} is blocking)
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
{{range .BlocklistInsights.Blocks}}
|
||||
<li><a href="/u/{{.Username}}">{{.Username}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h5 class="has-text-warning">Reverse List <span class="tag is-danger">{{len .BlocklistInsights.BlockedBy}}</span></h5>
|
||||
|
||||
<p>
|
||||
(Users who block {{.User.Username}})
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
{{range .BlocklistInsights.BlockedBy}}
|
||||
<li>
|
||||
<a href="/u/{{.Username}}">{{.Username}}</a>
|
||||
<small class="has-text-grey" title="{{.Date}}">{{.Date.Format "2006-01-02"}}</small>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .Intent "impersonate"}}
|
||||
<div class="block content">
|
||||
<h3>With great power...</h3>
|
||||
<p>
|
||||
|
|
Loading…
Reference in New Issue
Block a user