User endpoint to flag photos that should be Explicit

main
Noah Petherbridge 2024-03-16 13:29:28 -07:00
parent 04a7616299
commit d623f0bc3c
7 changed files with 405 additions and 21 deletions

View File

@ -0,0 +1,110 @@
package api
import (
"fmt"
"net/http"
"code.nonshy.com/nonshy/website/pkg/log"
"code.nonshy.com/nonshy/website/pkg/models"
"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 {
photo.Explicit = true
if err := photo.Save(); err != nil {
SendJSON(w, http.StatusBadRequest, Response{
Error: fmt.Sprintf("Couldn't save the photo: %s", err),
})
return
}
// 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,
})
})
}

View File

@ -0,0 +1,63 @@
package photo
import (
"net/http"
"strconv"
"strings"
"code.nonshy.com/nonshy/website/pkg/models"
"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 {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
photoID uint64
next = r.FormValue("next")
)
if !strings.HasPrefix(next, "/") {
next = "/"
}
// 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
}
if idInt, err := strconv.Atoi(r.FormValue("photo_id")); err == nil {
photoID = uint64(idInt)
} else {
session.FlashError(w, r, "Invalid or missing photo_id parameter: %s", err)
templates.Redirect(w, next)
return
}
// Get this photo.
photo, err := models.GetPhoto(photoID)
if err != nil {
session.FlashError(w, r, "Didn't find photo ID in database: %s", err)
templates.Redirect(w, next)
return
}
photo.Explicit = true
if err := photo.Save(); err != nil {
session.FlashError(w, r, "Couldn't save photo: %s", err)
} else {
session.Flash(w, r, "Marked photo as Explicit!")
}
// 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),
})
templates.Redirect(w, next)
})
}

View File

@ -111,6 +111,7 @@ func New() http.Handler {
mux.Handle("GET /v1/likes/users", middleware.LoginRequired(api.WhoLikes()))
mux.Handle("POST /v1/notifications/read", middleware.LoginRequired(api.ReadNotification()))
mux.Handle("POST /v1/notifications/delete", middleware.LoginRequired(api.ClearNotification()))
mux.Handle("POST /v1/photos/mark-explicit", middleware.LoginRequired(api.MarkPhotoExplicit()))
mux.Handle("GET /v1/comment-photos/remove-orphaned", api.RemoveOrphanedCommentPhotos())
mux.Handle("POST /v1/barertc/report", barertc.Report())
mux.Handle("POST /v1/barertc/profile", barertc.Profile())

View File

@ -134,6 +134,7 @@ var baseTemplates = []string{
config.TemplatePath + "/partials/user_avatar.html",
config.TemplatePath + "/partials/like_modal.html",
config.TemplatePath + "/partials/right_click.html",
config.TemplatePath + "/partials/mark_explicit.html",
config.TemplatePath + "/partials/themes.html",
}

View File

@ -0,0 +1,203 @@
<!--
A mechanism for users to flag a photo that should be marked as Explicit.
-->
{{define "mark-explicit-modal"}}
<div class="modal" id="markExplicitModal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="card">
<div class="card has-background-info">
<p class="card-header-title has-text-light">
Mark a photo as Explicit
</p>
</div>
<div class="card-content">
<form name="markExplicitForm" method="POST">
<div class="columns is-mobile mb-0">
<div class="column is-one-quarter">
<img>
</div>
<div class="column">
<div class="field">
<label class="label">What makes this photo explicit?</label>
<div>
<label class="checkbox">
<input type="radio"
name="reason"
value="Contains an erection">
Contains an erection
</label>
</div>
<div>
<label class="checkbox">
<input type="radio"
name="reason"
value="It's a close-up dick pic (whether flaccid or erect)">
It's a close-up dick pic (whether flaccid or erect)
</label>
</div>
<div>
<label class="checkbox">
<input type="radio"
name="reason"
value="It's a hole pic (ass/vagina/spread eagle/etc.)">
It's a hole pic (ass/vagina/spread eagle/etc.)
</label>
</div>
<div>
<label class="checkbox">
<input type="radio"
name="reason"
value="It's sexually suggestive (e.g. they're grabbing their junk or the photo is focused on their junk)">
It's sexually suggestive (e.g. they're grabbing their junk or the photo is focused on their junk)
</label>
</div>
<div>
<label class="checkbox">
<input type="radio"
name="reason"
value="It's sexually explicit (e.g. masturbation, sexual activity, porn)">
It's sexually explicit (e.g. masturbation, sexual activity, porn)
</label>
</div>
<div class="columns is-mobile is-gapless">
<div class="column is-narrow mr-2">
<label class="checkbox">
<input type="radio"
name="reason"
value="Other"
onclick="document.querySelector('#markExplicitFormOther').focus()">
Other:
</label>
</div>
<div class="column">
<input type="text" class="input is-size-7"
id="markExplicitFormOther"
name="other"
placeholder="Please elaborate">
</div>
</div>
</div>
</div>
</div>
<p class="help mb-1">
Note: if the photo shows a mild lifestyle device (cock ring, genital piercings, chastity cage, etc.) but
is otherwise not a sexually charged photo (e.g. it is just a normal full-body nude pic),
it is not considered by {{PrettyTitle}} to be an explicit photo.
</p>
<div class="has-text-centered">
<button type="submit" class="button is-success">
Submit
</button>
<button type="reset" class="button is-secondary">
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const $modal = document.querySelector("#markExplicitModal"),
$modalBG = $modal.querySelector(".modal-background"),
$img = $modal.getElementsByTagName("img")[0],
$form = $modal.getElementsByTagName("form")[0],
$submit = $form.querySelector("button[type=submit]"),
$reset = $form.querySelector("button[type=reset]");
// The active photo being inspected and the payload to post.
let $node = null; // last clicked link, to remove on successful post.
let payload = {
photoID: 0,
reason: "",
other: "",
};
// Modal toggle function.
function showHide(v) {
if (v) {
$modal.classList.add("is-active");
} else {
$modal.classList.remove("is-active");
$form.reset();
}
}
// Closing the modal.
$modalBG.addEventListener("click", () => {
showHide(false);
});
$reset.addEventListener("click", () => {
showHide(false);
});
// Submit the form.
$form.onsubmit = (e) => {
e.preventDefault();
e.stopPropagation();
payload.reason = $form.reason.value;
payload.other = $form.other.value;
fetch("/v1/photos/mark-explicit", {
method: "POST",
mode: "same-origin",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
})
.then((response) => response.json())
.then((data) => {
if (data.StatusCode !== 200) {
window.alert(data.data.error);
return;
}
showHide(false);
// Replace the flag link the user clicked on to get here.
if ($node !== null) {
$node.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
});
$node.innerHTML = `<i class="fa fa-check mr-1"></i> You have flagged that this photo should be Explicit`;
}
setTimeout(() => {
window.alert("This photo has been flagged to be marked as Explicit. Thanks for your help!");
}, 200);
}).catch(resp => {
window.alert(resp);
});
};
// Find the "mark this as explicit" links around the page.
(document.querySelectorAll(".nonshy-mark-explicit") || []).forEach(node => {
let photoID = node.dataset.photoId,
photoURL = node.dataset.photoUrl;
node.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
$img.src = photoURL;
payload.photoID = parseInt(photoID);
$node = node;
showHide(true);
});
});
})
</script>
{{end}}

View File

@ -540,14 +540,14 @@
{{template "card-body" .}}
<!-- Admin actions: quick mark photo as explicit -->
{{if and ($Root.CurrentUser.IsAdmin) (not .Explicit)}}
<!-- Quick mark photo as explicit -->
{{if not .Explicit}}
<div class="mt-2">
<a href="/admin/photo/mark-explicit?photo_id={{.ID}}&next={{$Root.Request.URL.Path}}"
class="has-text-danger is-size-7"
onclick="return confirm('Do you want to mark this photo as Explicit?')">
<i class="fa fa-peace mr-1"></i>
Mark photo as Explicit
<a href="#"
class="has-text-danger is-size-7 nonshy-mark-explicit"
data-photo-id="{{.ID}}" data-photo-url="{{PhotoURL .Filename}}">
<i class="fa fa-fire mr-1"></i>
Should this photo be marked Explicit?
</a>
</div>
{{end}}
@ -666,14 +666,14 @@
{{template "card-body" .}}
<!-- Admin actions: quick mark photo as explicit -->
{{if and ($Root.CurrentUser.IsAdmin) (not .Explicit)}}
<!-- Quick mark photo as explicit -->
{{if not .Explicit}}
<div class="mt-2">
<a href="/admin/photo/mark-explicit?photo_id={{.ID}}&next={{$Root.Request.URL}}"
class="has-text-danger is-size-7"
onclick="return confirm('Do you want to mark this photo as Explicit?')">
<i class="fa fa-peace mr-1"></i>
Mark photo as Explicit
<a href="#"
class="has-text-danger is-size-7 nonshy-mark-explicit"
data-photo-id="{{.ID}}" data-photo-url="{{PhotoURL .Filename}}">
<i class="fa fa-fire mr-1"></i>
Should this photo be marked Explicit?
</a>
</div>
{{end}}
@ -794,4 +794,7 @@
});
});
</script>
<!-- Mark Explicit modal -->
{{template "mark-explicit-modal" .}}
{{end}}

View File

@ -98,14 +98,14 @@
</div>
{{end}}
<!-- Admin actions: quick mark photo as explicit -->
{{if and (.CurrentUser.IsAdmin) (not .Photo.Explicit)}}
<!-- Quick mark photo as explicit -->
{{if not .Photo.Explicit}}
<div class="mt-2">
<a href="/admin/photo/mark-explicit?photo_id={{.Photo.ID}}&next={{.Request.URL}}"
class="has-text-danger is-size-7"
onclick="return confirm('Do you want to mark this photo as Explicit?')">
<i class="fa fa-peace mr-1"></i>
Mark photo as Explicit
<a href="#"
class="has-text-danger is-size-7 nonshy-mark-explicit"
data-photo-id="{{.Photo.ID}}" data-photo-url="{{PhotoURL .Photo.Filename}}">
<i class="fa fa-fire mr-1"></i>
Should this photo be marked Explicit?
</a>
</div>
{{end}}
@ -333,4 +333,7 @@
</div>
</div>
<!-- Mark Explicit modal -->
{{template "mark-explicit-modal" .}}
{{end}}