diff --git a/frontend/src/pages/ReportedPhotos.tsx b/frontend/src/pages/ReportedPhotos.tsx index 8766b0a..a02f1b0 100644 --- a/frontend/src/pages/ReportedPhotos.tsx +++ b/frontend/src/pages/ReportedPhotos.tsx @@ -86,16 +86,36 @@ export default function ReportedPhotos() { return } - if ( - !confirm( - `Submit ${decisionsList.length} decision(s)?\n\nThis will ${ - decisionsList.filter((d) => d.decision === 'remove').length - } remove photo(s) and ${ - decisionsList.filter((d) => d.decision === 'keep').length - } keep photo(s).` - ) - ) { - return + // Check if there are any 'remove' decisions + const removeDecisions = decisionsList.filter((d) => d.decision === 'remove') + const keepDecisions = decisionsList.filter((d) => d.decision === 'keep') + + // Show specific confirmation for removal + if (removeDecisions.length > 0) { + const removeCount = removeDecisions.length + const photoDetails = removeDecisions + .map((d) => { + const reported = reportedPhotos.find((p) => p.id === d.id) + return reported?.photo_filename || `Photo #${reported?.photo_id || d.id}` + }) + .join('\n - ') + + const confirmMessage = `⚠️ WARNING: You are about to PERMANENTLY REMOVE ${removeCount} photo(s):\n\n - ${photoDetails}\n\nThis will:\n • Delete the photo(s) from the database\n • Delete all faces detected in the photo(s)\n • Delete all encodings related to those faces\n\nThis action CANNOT be undone!\n\nAre you sure you want to proceed?` + + if (!confirm(confirmMessage)) { + return + } + } + + // Show general confirmation if there are also 'keep' decisions + if (keepDecisions.length > 0) { + const confirmMessage = `Submit ${decisionsList.length} decision(s)?\n\nThis will ${ + removeDecisions.length + } remove photo(s) and ${keepDecisions.length} keep photo(s).` + + if (!confirm(confirmMessage)) { + return + } } setSubmitting(true) @@ -220,6 +240,7 @@ export default function ReportedPhotos() { {reportedPhotos.map((reported) => { const isReviewed = reported.status === 'reviewed' const isDismissed = reported.status === 'dismissed' + const canMakeDecision = !isDismissed && (reported.status === 'pending' || reported.status === 'reviewed') return ( -
- - -
+ {canMakeDecision ? ( +
+ + +
+ ) : ( + - + )} {isReviewed || isDismissed ? ( diff --git a/src/web/api/reported_photos.py b/src/web/api/reported_photos.py index 73e9fc1..7955f82 100644 --- a/src/web/api/reported_photos.py +++ b/src/web/api/reported_photos.py @@ -11,7 +11,7 @@ from sqlalchemy import text from sqlalchemy.orm import Session from src.web.db.session import get_auth_db, get_db -from src.web.db.models import Photo +from src.web.db.models import Photo, PhotoTagLinkage from src.web.api.users import get_current_admin_user router = APIRouter(prefix="/reported-photos", tags=["reported-photos"]) @@ -224,14 +224,19 @@ def review_reported_photos( kept_count += 1 # Count as kept since we couldn't remove it continue - # Delete the photo (cascade will delete faces, tags, etc.) + # Delete tag linkages for this photo + main_db.query(PhotoTagLinkage).filter( + PhotoTagLinkage.photo_id == photo.id + ).delete(synchronize_session=False) + + # Delete the photo (cascade will delete faces, etc.) main_db.delete(photo) main_db.commit() - # Update status in auth database + # Update status in auth database to dismissed auth_db.execute(text(""" UPDATE inappropriate_photo_reports - SET status = 'reviewed', + SET status = 'dismissed', reviewed_at = :reviewed_at, reviewed_by = :reviewed_by, review_notes = :review_notes