From e146c09850de0aa51be445a65acc17449907816a Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 17 Oct 2024 19:21:18 -0700 Subject: [PATCH] Improvements to Feedback & Reports * Add an AboutUserID field to feedbacks, so when the report is about a picture that is later deleted, the feedback can still link to the original owner's account instead of showing an error. * Add filters to the User Notes page so the admin can see: * All feedback From or About the user or their content (default) * Feedback created by the user * Feedback about the user or their content * Fuzzy search for any feedback containing the user's name. * On chat room reports: make the @channel ID a clickable user profile link for convenience. --- pkg/controller/account/user_note.go | 8 +++- pkg/controller/admin/feedback.go | 33 +++++++++++--- .../api/barertc/barertc_webhooks.go | 8 +++- pkg/controller/index/contact.go | 13 +++++- pkg/models/feedback.go | 44 ++++++++++++++++--- web/templates/account/user_notes.html | 41 ++++++++++++++--- 6 files changed, 122 insertions(+), 25 deletions(-) diff --git a/pkg/controller/account/user_note.go b/pkg/controller/account/user_note.go index 4c7e0a3..9554075 100644 --- a/pkg/controller/account/user_note.go +++ b/pkg/controller/account/user_note.go @@ -18,7 +18,10 @@ func UserNotes() http.HandlerFunc { tmpl := templates.Must("account/user_notes.html") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Parse the username out of the URL parameters. - var username = r.PathValue("username") + var ( + username = r.PathValue("username") + show = r.FormValue("show") // admin feedback filter + ) // Find this user. user, err := models.FindUser(username) @@ -108,7 +111,7 @@ func UserNotes() http.HandlerFunc { } // Paginate feedback & reports. - if fb, err := models.PaginateFeedbackAboutUser(user, fbPager); err != nil { + if fb, err := models.PaginateFeedbackAboutUser(user, show, fbPager); err != nil { session.FlashError(w, r, "Paginating feedback on this user: %s", err) } else { feedback = fb @@ -141,6 +144,7 @@ func UserNotes() http.HandlerFunc { "MyNote": myNote, // Admin concerns. + "Show": show, "Feedback": feedback, "FeedbackPager": fbPager, "OtherNotes": otherNotes, diff --git a/pkg/controller/admin/feedback.go b/pkg/controller/admin/feedback.go index de96f21..39a17a3 100644 --- a/pkg/controller/admin/feedback.go +++ b/pkg/controller/admin/feedback.go @@ -69,6 +69,15 @@ func Feedback() http.HandlerFunc { // Are we visiting a linked resource (via TableID)? if fb != nil && fb.TableID > 0 && visit { + // New (Oct 17 '24): feedbacks may carry an AboutUserID, e.g. for photos in case the reported + // photo is removed then the associated owner of the photo is still carried in the report. + var aboutUser *models.User + if fb.AboutUserID > 0 { + if user, err := models.GetUser(fb.AboutUserID); err == nil { + aboutUser = user + } + } + switch fb.TableName { case "users": user, err := models.GetUser(fb.TableID) @@ -81,15 +90,29 @@ func Feedback() http.HandlerFunc { case "photos": pic, err := models.GetPhoto(fb.TableID) if err != nil { + // If there was an About User, visit their profile page instead. + if aboutUser != nil { + session.FlashError(w, r, "The photo #%d was deleted, visiting the owner's profile page instead.", fb.TableID) + templates.Redirect(w, "/u/"+aboutUser.Username) + return + } + session.FlashError(w, r, "Couldn't get photo %d: %s", fb.TableID, err) } else { // Going to the user's profile page? if profile { - user, err := models.GetUser(pic.UserID) - if err != nil { - session.FlashError(w, r, "Couldn't visit user %d: %s", fb.TableID, err) - } else { - templates.Redirect(w, "/u/"+user.Username) + + // Going forward: the aboutUser will be populated, this is for legacy reports. + if aboutUser == nil { + if user, err := models.GetUser(pic.UserID); err == nil { + aboutUser = user + } else { + session.FlashError(w, r, "Couldn't visit user %d: %s", fb.TableID, err) + } + } + + if aboutUser != nil { + templates.Redirect(w, "/u/"+aboutUser.Username) return } } diff --git a/pkg/controller/api/barertc/barertc_webhooks.go b/pkg/controller/api/barertc/barertc_webhooks.go index a356e6f..7c07c17 100644 --- a/pkg/controller/api/barertc/barertc_webhooks.go +++ b/pkg/controller/api/barertc/barertc_webhooks.go @@ -79,6 +79,9 @@ func Report() http.HandlerFunc { log.Debug("Got chat report: %+v", report) + // Make a clickable profile link for the channel ID (other user). + otherUsername := strings.TrimPrefix(report.Channel, "@") + // Create an admin Feedback model. fb := &models.Feedback{ Intent: "report", @@ -87,7 +90,7 @@ func Report() http.HandlerFunc { "A message was reported on the chat room!\n\n"+ "* From username: [%s](/u/%s)\n"+ "* About username: [%s](/u/%s)\n"+ - "* Channel: **%s**\n"+ + "* Channel: [**%s**](/u/%s)\n"+ "* Timestamp: %s\n"+ "* Classification: %s\n"+ "* User comment: %s\n\n"+ @@ -95,7 +98,7 @@ func Report() http.HandlerFunc { "The reported message on chat was:\n\n%s", report.FromUsername, report.FromUsername, report.AboutUsername, report.AboutUsername, - report.Channel, + report.Channel, otherUsername, report.Timestamp, report.Reason, report.Comment, @@ -116,6 +119,7 @@ func Report() http.HandlerFunc { if err == nil { fb.TableName = "users" fb.TableID = targetUser.ID + fb.AboutUserID = targetUser.ID } else { log.Error("BareRTC Chat Feedback: couldn't find user ID for AboutUsername=%s: %s", report.AboutUsername, err) } diff --git a/pkg/controller/index/contact.go b/pkg/controller/index/contact.go index 849c315..1e7d197 100644 --- a/pkg/controller/index/contact.go +++ b/pkg/controller/index/contact.go @@ -32,8 +32,9 @@ func Contact() http.HandlerFunc { trap2 = r.FormValue("comment") != "" tableID int tableName string - tableLabel string // front-end user feedback about selected report item - messageRequired = true // unless we have a table ID to work with + tableLabel string // front-end user feedback about selected report item + aboutUser *models.User // associated user (e.g. owner of reported photo) + messageRequired = true // unless we have a table ID to work with success = "Thank you for your feedback! Your message has been delivered to the website administrators." ) @@ -56,6 +57,7 @@ func Contact() http.HandlerFunc { tableName = "users" if user, err := models.GetUser(uint64(tableID)); err == nil { tableLabel = fmt.Sprintf(`User account "%s"`, user.Username) + aboutUser = user } else { log.Error("/contact: couldn't produce table label for user %d: %s", tableID, err) } @@ -66,6 +68,7 @@ func Contact() http.HandlerFunc { if pic, err := models.GetPhoto(uint64(tableID)); err == nil { if user, err := models.GetUser(pic.UserID); err == nil { tableLabel = fmt.Sprintf(`A profile photo of user account "%s"`, user.Username) + aboutUser = user } else { log.Error("/contact: couldn't produce table label for user %d: %s", tableID, err) } @@ -81,6 +84,7 @@ func Contact() http.HandlerFunc { var username = "[unavailable]" if sender, err := models.GetUser(msg.SourceUserID); err == nil { username = sender.Username + aboutUser = sender } footer = fmt.Sprintf(` @@ -100,6 +104,7 @@ From: @%s // Find this comment. if comment, err := models.GetComment(uint64(tableID)); err == nil { tableLabel = fmt.Sprintf(`A comment written by "%s"`, comment.User.Username) + aboutUser = &comment.User } else { log.Error("/contact: couldn't produce table label for comment %d: %s", tableID, err) } @@ -166,6 +171,10 @@ From: @%s TableID: uint64(tableID), } + if aboutUser != nil { + fb.AboutUserID = aboutUser.ID + } + if currentUser != nil && currentUser.ID > 0 { fb.UserID = currentUser.ID } else if replyTo != "" { diff --git a/pkg/models/feedback.go b/pkg/models/feedback.go index 9e7c28c..37809cd 100644 --- a/pkg/models/feedback.go +++ b/pkg/models/feedback.go @@ -12,6 +12,7 @@ import ( type Feedback struct { ID uint64 `gorm:"primaryKey"` UserID uint64 `gorm:"index"` // if logged-in user posted this + AboutUserID uint64 // associated 'about' user (e.g., owner of a reported photo) Acknowledged bool `gorm:"index"` // admin dashboard "read" status Intent string Subject string @@ -99,20 +100,49 @@ func PaginateFeedback(acknowledged bool, intent, subject string, search *Search, // It returns reports where table_name=users and their user ID, or where table_name=photos and about any // of their current photo IDs. Additionally, it will look for chat room reports which were about their // username. -func PaginateFeedbackAboutUser(user *User, pager *Pagination) ([]*Feedback, error) { +// +// The 'show' parameter applies some basic filter choices: +// +// - Blank string (default) = all reports From or About this user +// - "about" = all reports About this user (by table_name=users table_id=userID, or table_name=photos +// for any of their existing photo IDs) +// - "from" = all reports From this user (where reporting user_id is the user's ID) +// - "fuzzy" = fuzzy full text search on all reports that contain the user's username. +func PaginateFeedbackAboutUser(user *User, show string, pager *Pagination) ([]*Feedback, error) { var ( fb = []*Feedback{} photoIDs, _ = user.AllPhotoIDs() wheres = []string{} placeholders = []interface{}{} + like = "%" + user.Username + "%" ) - wheres = append(wheres, ` - (table_name = 'users' AND table_id = ?) OR - (table_name = 'photos' AND table_id IN ?) OR - message LIKE ? - `) - placeholders = append(placeholders, user.ID, photoIDs, user.Username) + // How to apply the search filters? + switch show { + case "about": + wheres = append(wheres, ` + about_user_id = ? OR + (table_name = 'users' AND table_id = ?) OR + (table_name = 'photos' AND table_id IN ?) + `) + placeholders = append(placeholders, user.ID, user.ID, photoIDs) + case "from": + wheres = append(wheres, "user_id = ?") + placeholders = append(placeholders, user.ID) + case "fuzzy": + wheres = append(wheres, "message LIKE ?") + placeholders = append(placeholders, like) + default: + // Default=everything. + wheres = append(wheres, ` + user_id = ? OR + about_user_id = ? OR + (table_name = 'users' AND table_id = ?) OR + (table_name = 'photos' AND table_id IN ?) OR + message LIKE ? + `) + placeholders = append(placeholders, user.ID, user.ID, user.ID, photoIDs, like) + } query := DB.Where( strings.Join(wheres, " AND "), diff --git a/web/templates/account/user_notes.html b/web/templates/account/user_notes.html index cf32382..101a0ca 100644 --- a/web/templates/account/user_notes.html +++ b/web/templates/account/user_notes.html @@ -187,11 +187,38 @@
{{if .FeedbackPager.Total}} - +
Found {{.FeedbackPager.Total}} report{{Pluralize64 .FeedbackPager.Total}} about this user (page {{.FeedbackPager.Page}} of {{.FeedbackPager.Pages}}). - +
{{end}} + +
+
+
+ +
+
+
+ +
+
+
+ Reset + +
+
+
+
{{SimplePager .FeedbackPager}}
@@ -224,29 +251,29 @@ {{if ne .TableID 0}} - {{.TableID}}{{end}} {{else if eq .TableName "users"}} Users: {{.TableID}} - {{else if eq .TableName "photos"}} Photos: {{.TableID}} - - {{else if eq .TableName "messages"}} Messages: {{.TableID}} - {{else}} {{.TableName}}: {{.TableID}} - + {{end}}