website/pkg/controller/admin/feedback.go
Noah 6c91c67c97 More Private User Avatars
* Users who set their Profile Picture to "friends only" or "private" can have
  their avatar be private all over the website to users who are not their
  friends or not granted access.
* Users who are not your friends see a yellow placeholder avatar, and users
  not granted access to a private Profile Pic sees a purple avatar.
* Admin users see these same placeholder avatars most places too (on search,
  forums, comments, etc.) if the user did not friend or grant the admin. But
  admins ALWAYS see it on their Profile Page directly, for ability to moderate.
* Fix marking Notifications as read: clicking the link in an unread notification
  now will wait on the ajax request to finish before allowing the redirect.
* Update the FAQ
2022-09-08 21:42:20 -07:00

177 lines
5.1 KiB
Go

package admin
import (
"fmt"
"net/http"
"strconv"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/photo"
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates"
)
// Feedback controller (/admin/feedback)
func Feedback() http.HandlerFunc {
tmpl := templates.Must("admin/feedback.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Query params.
var (
acknowledged = r.FormValue("acknowledged") == "true"
intent = r.FormValue("intent")
visit = r.FormValue("visit") == "true" // visit the linked table ID
profile = r.FormValue("profile") == "true" // visit associated user profile
verdict = r.FormValue("verdict")
fb *models.Feedback
)
currentUser, err := session.CurrentUser(r)
if err != nil {
session.FlashError(w, r, "Couldn't get your current user: %s", err)
}
// Working on a target message?
if idStr := r.FormValue("id"); idStr != "" {
if idInt, err := strconv.Atoi(idStr); err != nil {
session.FlashError(w, r, "Couldn't parse id param: %s", err)
} else {
fb, err = models.GetFeedback(uint64(idInt))
if err != nil {
session.FlashError(w, r, "Couldn't load feedback message %d: %s", idInt, err)
}
}
}
// Are we visiting a linked resource (via TableID)?
if fb != nil && fb.TableID > 0 && visit {
switch fb.TableName {
case "users":
user, err := models.GetUser(fb.TableID)
if err != nil {
session.FlashError(w, r, "Couldn't visit user %d: %s", fb.TableID, err)
} else {
templates.Redirect(w, "/u/"+user.Username)
return
}
case "photos":
pic, err := models.GetPhoto(fb.TableID)
if err != nil {
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)
return
}
}
// Direct link to the photo.
templates.Redirect(w, photo.URLPath(pic.Filename))
return
}
case "messages":
// To read this message we will need to impersonate the reporter.
user, err := models.GetUser(fb.UserID)
if err != nil {
session.FlashError(w, r, "Couldn't get reporting user ID %d: %s", fb.UserID, err)
} else {
if err := session.ImpersonateUser(w, r, user, currentUser); err != nil {
session.FlashError(w, r, "Couldn't impersonate user: %s", err)
} else {
// Redirect to the thread.
session.Flash(w, r, "NOTICE: You are now impersonating %s to view their inbox.", user.Username)
templates.Redirect(w, fmt.Sprintf("/messages/read/%d", fb.TableID))
return
}
}
case "comments":
// Get this comment.
comment, err := models.GetComment(fb.TableID)
if err != nil {
session.FlashError(w, r, "Couldn't get comment ID %d: %s", fb.TableID, err)
} else {
// What was the comment on?
switch comment.TableName {
case "threads":
// Visit the thread.
templates.Redirect(w, fmt.Sprintf("/forum/thread/%d", comment.TableID))
return
}
}
default:
session.FlashError(w, r, "Couldn't visit TableID %s/%d: not a supported TableName", fb.TableName, fb.TableID)
}
}
// Are we (un)acknowledging a message?
if r.Method == http.MethodPost {
if fb == nil {
session.FlashError(w, r, "Missing feedback ID for this POST!")
} else {
switch verdict {
case "acknowledge":
fb.Acknowledged = true
if err := fb.Save(); err != nil {
session.FlashError(w, r, "Couldn't save message: %s", err)
} else {
session.Flash(w, r, "Message acknowledged!")
}
case "unacknowledge":
fb.Acknowledged = false
if err := fb.Save(); err != nil {
session.FlashError(w, r, "Couldn't save message: %s", err)
} else {
session.Flash(w, r, "Message acknowledged!")
}
default:
session.FlashError(w, r, "Unsupported verdict: %s", verdict)
}
}
templates.Redirect(w, r.URL.Path)
return
}
// Get the feedback.
pager := &models.Pagination{
Page: 1,
PerPage: config.PageSizeAdminFeedback,
Sort: "updated_at desc",
}
pager.ParsePage(r)
page, err := models.PaginateFeedback(acknowledged, intent, pager)
if err != nil {
session.FlashError(w, r, "Couldn't load feedback from DB: %s", err)
}
// Map user IDs.
var userIDs = []uint64{}
for _, p := range page {
if p.UserID > 0 {
userIDs = append(userIDs, p.UserID)
}
}
userMap, err := models.MapUsers(currentUser, userIDs)
if err != nil {
session.FlashError(w, r, "Couldn't map user IDs: %s", err)
}
var vars = map[string]interface{}{
"Intent": intent,
"Acknowledged": acknowledged,
"Feedback": page,
"UserMap": userMap,
"Pager": pager,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}