website/pkg/models/feedback.go
Noah Petherbridge e146c09850 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.
2024-10-17 19:21:18 -07:00

190 lines
5.0 KiB
Go

package models
import (
"sort"
"strings"
"time"
"code.nonshy.com/nonshy/website/pkg/log"
)
// Feedback table for Contact Us & Reporting to admins.
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
Message string
TableName string
TableID uint64
ReplyTo string // logged-out user may leave their email for reply
CreatedAt time.Time
UpdatedAt time.Time
}
// GetFeedback by ID.
func GetFeedback(id uint64) (*Feedback, error) {
m := &Feedback{}
result := DB.First(&m, id)
return m, result.Error
}
// CountUnreadFeedback gets the count of unacknowledged feedback for admins.
func CountUnreadFeedback() int64 {
query := DB.Where(
"acknowledged = ?",
false,
)
var count int64
result := query.Model(&Feedback{}).Count(&count)
if result.Error != nil {
log.Error("models.CountUnreadFeedback: %s", result.Error)
}
return count
}
// PaginateFeedback
func PaginateFeedback(acknowledged bool, intent, subject string, search *Search, pager *Pagination) ([]*Feedback, error) {
var (
fb = []*Feedback{}
wheres = []string{}
placeholders = []interface{}{}
)
wheres = append(wheres, "acknowledged = ?")
placeholders = append(placeholders, acknowledged)
if intent != "" {
wheres = append(wheres, "intent = ?")
placeholders = append(placeholders, intent)
}
if subject != "" {
wheres = append(wheres, "subject = ?")
placeholders = append(placeholders, subject)
}
// Search terms.
for _, term := range search.Includes {
var ilike = "%" + strings.ToLower(term) + "%"
wheres = append(wheres, "message ILIKE ?")
placeholders = append(placeholders, ilike)
}
for _, term := range search.Excludes {
var ilike = "%" + strings.ToLower(term) + "%"
wheres = append(wheres, "message NOT ILIKE ?")
placeholders = append(placeholders, ilike)
}
query := DB.Where(
strings.Join(wheres, " AND "),
placeholders...,
).Order(
pager.Sort,
)
query.Model(&Feedback{}).Count(&pager.Total)
result := query.Offset(
pager.GetOffset(),
).Limit(pager.PerPage).Find(&fb)
return fb, result.Error
}
// PaginateFeedbackAboutUser digs through feedback about a specific user ID or one of their Photos.
//
// 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.
//
// 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 + "%"
)
// 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 "),
placeholders...,
).Order(
pager.Sort,
)
query.Model(&Feedback{}).Count(&pager.Total)
result := query.Offset(
pager.GetOffset(),
).Limit(pager.PerPage).Find(&fb)
return fb, result.Error
}
// DistinctFeedbackSubjects returns the distinct subjects on feedback & reports.
func DistinctFeedbackSubjects() []string {
var results = []string{}
query := DB.Model(&Feedback{}).
Select("DISTINCT feedbacks.subject").
Group("feedbacks.subject").
Find(&results)
if query.Error != nil {
log.Error("DistinctFeedbackSubjects: %s", query.Error)
return nil
}
sort.Strings(results)
return results
}
// CreateFeedback saves a new Feedback row to the DB.
func CreateFeedback(fb *Feedback) error {
result := DB.Create(fb)
return result.Error
}
// Save Feedback.
func (fb *Feedback) Save() error {
result := DB.Save(fb)
return result.Error
}