542d0bb300
When a user marks that another photo should have been marked as explicit: * The owner of that photo gets a notification about it, which reminds them of the explicit photo policy. * The photo's "Flagged" boolean is set (along with the Explicit boolean) * The 'Edit' page on a Flagged photo shows a red banner above the Explicit option, explaining that it was flagged. The checkbox text is crossed-out, with a "no" cursor and title text over - but can still be unchecked. If the user removes the Explicit flag on a flagged photo and saves it: * An admin report is generated to notify to take a look too. * The Explicit flag is cleared as normal * The Flagged boolean is also cleared on this photo: if they set it back to Explicit again themselves, the red banner won't appear and it won't notify again - unless a community member flagged it again! Also makes some improvements to the admin page: * On photo reports: show a blurred-out (clickable to reveal) photo on feedback items about photos.
150 lines
4.3 KiB
Go
150 lines
4.3 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"code.nonshy.com/nonshy/website/pkg/config"
|
|
"code.nonshy.com/nonshy/website/pkg/log"
|
|
"code.nonshy.com/nonshy/website/pkg/models"
|
|
"code.nonshy.com/nonshy/website/pkg/ratelimit"
|
|
"code.nonshy.com/nonshy/website/pkg/session"
|
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
|
)
|
|
|
|
// User endpoint to flag other photos as explicit on their behalf.
|
|
func MarkPhotoExplicit() http.HandlerFunc {
|
|
// Request JSON schema.
|
|
type Request struct {
|
|
PhotoID uint64 `json:"photoID"`
|
|
Reason string `json:"reason"`
|
|
Other string `json:"other"`
|
|
}
|
|
|
|
// Response JSON schema.
|
|
type Response struct {
|
|
OK bool `json:"OK"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Get current user.
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Failed to get current user: %s", err)
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
// Parse request payload.
|
|
var req Request
|
|
if err := ParseJSON(r, &req); err != nil {
|
|
SendJSON(w, http.StatusBadRequest, Response{
|
|
Error: fmt.Sprintf("Error with request payload: %s", err),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Form validation.
|
|
if req.Reason == "" {
|
|
SendJSON(w, http.StatusBadRequest, Response{
|
|
Error: "Please select one of the reasons why this photo should've been marked Explicit.",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Get this photo.
|
|
photo, err := models.GetPhoto(req.PhotoID)
|
|
if err != nil {
|
|
SendJSON(w, http.StatusBadRequest, Response{
|
|
Error: "That photo was not found!",
|
|
})
|
|
return
|
|
}
|
|
|
|
if !photo.Explicit {
|
|
// Rate limit how frequently they are tagging photos, in case a user is just going around
|
|
// and tagging EVERYTHING.
|
|
if !currentUser.IsAdmin {
|
|
limiter := &ratelimit.Limiter{
|
|
Namespace: "mark_explicit",
|
|
ID: currentUser.ID,
|
|
Limit: config.MarkExplicitRateLimit,
|
|
Window: config.MarkExplicitRateLimitWindow,
|
|
CooldownAt: config.MarkExplicitRateLimitCooldownAt,
|
|
Cooldown: config.MarkExplicitRateLimitCooldown,
|
|
}
|
|
if err := limiter.Ping(); err != nil {
|
|
SendJSON(w, http.StatusTooManyRequests, Response{
|
|
Error: "We appreciate the enthusiasm, but you seem to be marking an unusually high number of photos!\n\n" + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
photo.Explicit = true
|
|
photo.Flagged = true
|
|
if err := photo.Save(); err != nil {
|
|
SendJSON(w, http.StatusBadRequest, Response{
|
|
Error: fmt.Sprintf("Couldn't save the photo: %s", err),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Send the photo's owner a notification so they are aware.
|
|
if owner, err := models.GetUser(photo.UserID); err == nil {
|
|
notif := &models.Notification{
|
|
UserID: owner.ID,
|
|
AboutUser: *owner,
|
|
Type: models.NotificationExplicitPhoto,
|
|
TableName: "photos",
|
|
TableID: photo.ID,
|
|
Link: fmt.Sprintf("/photo/view?id=%d", photo.ID),
|
|
}
|
|
if err := models.CreateNotification(notif); err != nil {
|
|
log.Error("Couldn't create Likes notification: %s", err)
|
|
}
|
|
} else {
|
|
log.Error("MarkExplicit: getting photo owner (%d): %s", photo.UserID, err)
|
|
}
|
|
|
|
// If a non-admin user has hit this API, log an admin report for visibility and
|
|
// to keep a pulse on things (e.g. in case of abuse).
|
|
if !currentUser.IsAdmin {
|
|
fb := &models.Feedback{
|
|
Intent: "report",
|
|
Subject: "User flagged an explicit photo",
|
|
UserID: currentUser.ID,
|
|
TableName: "photos",
|
|
TableID: photo.ID,
|
|
Message: fmt.Sprintf(
|
|
"A user has flagged that a photo should have been marked as Explicit.\n\n"+
|
|
"* Reported by: %s (ID %d)\n"+
|
|
"* Reason given: %s\n"+
|
|
"* Elaboration (if other): %s\n\n"+
|
|
"The photo had been immediately marked as Explicit.",
|
|
currentUser.Username,
|
|
currentUser.ID,
|
|
req.Reason,
|
|
req.Other,
|
|
),
|
|
}
|
|
|
|
// Save the feedback.
|
|
if err := models.CreateFeedback(fb); err != nil {
|
|
log.Error("Couldn't save feedback from user updating their DOB: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Log the change.
|
|
models.LogUpdated(&models.User{ID: photo.UserID}, currentUser, "photos", photo.ID, "Marked explicit by admin action.", []models.FieldDiff{
|
|
models.NewFieldDiff("Explicit", false, true),
|
|
})
|
|
|
|
SendJSON(w, http.StatusOK, Response{
|
|
OK: true,
|
|
})
|
|
})
|
|
}
|