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, }) }) }