feat: Enhance reported photos management with cleanup functionality and report comment

This commit introduces a new cleanup feature for reported photos, allowing admins to delete records based on their review status. The API has been updated with a new endpoint for cleanup operations, and the frontend now includes a button to trigger this action. Additionally, a report comment field has been added to the reported photo response model, improving the detail available for each reported photo. The user interface has been updated to display report comments and provide a confirmation dialog for the cleanup action. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09
2025-11-25 13:08:40 -05:00
parent 51eaf6a52b
commit f9e8c476bc
4 changed files with 220 additions and 45 deletions
+89 -8
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import datetime
from typing import Annotated, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, ConfigDict
from sqlalchemy import text
from sqlalchemy.orm import Session
@@ -32,6 +32,7 @@ class ReportedPhotoResponse(BaseModel):
reviewed_at: Optional[str] = None
reviewed_by: Optional[int] = None
review_notes: Optional[str] = None
report_comment: Optional[str] = None
# Photo details from main database
photo_path: Optional[str] = None
photo_filename: Optional[str] = None
@@ -74,6 +75,16 @@ class ReviewResponse(BaseModel):
errors: list[str]
class CleanupResponse(BaseModel):
"""Response payload for cleanup operations."""
model_config = ConfigDict(protected_namespaces=())
deleted_records: int
errors: list[str]
warnings: list[str] = []
@router.get("", response_model=ReportedPhotosListResponse)
def list_reported_photos(
current_admin: Annotated[dict, Depends(get_current_admin_user)],
@@ -102,7 +113,8 @@ def list_reported_photos(
ipr.reported_at,
ipr.reviewed_at,
ipr.reviewed_by,
ipr.review_notes
ipr.review_notes,
ipr.report_comment
FROM inappropriate_photo_reports ipr
LEFT JOIN users u ON ipr.user_id = u.id
WHERE ipr.status = :status_filter
@@ -120,7 +132,8 @@ def list_reported_photos(
ipr.reported_at,
ipr.reviewed_at,
ipr.reviewed_by,
ipr.review_notes
ipr.review_notes,
ipr.report_comment
FROM inappropriate_photo_reports ipr
LEFT JOIN users u ON ipr.user_id = u.id
ORDER BY ipr.reported_at DESC
@@ -148,6 +161,7 @@ def list_reported_photos(
reviewed_at=str(row.reviewed_at) if row.reviewed_at else None,
reviewed_by=row.reviewed_by,
review_notes=row.review_notes,
report_comment=row.report_comment,
photo_path=photo_path,
photo_filename=photo_filename,
))
@@ -205,11 +219,9 @@ def review_reported_photos(
# Delete photo from main database (cascade will handle related records)
photo = main_db.query(Photo).filter(Photo.id == row.photo_id).first()
if not photo:
errors.append(f"Photo {row.photo_id} not found in main database")
# Still update status to reviewed since we can't process it
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
@@ -218,10 +230,10 @@ def review_reported_photos(
"id": decision.id,
"reviewed_at": now,
"reviewed_by": admin_user_id,
"review_notes": decision.review_notes or "Photo not found in database"
"review_notes": decision.review_notes or "Photo not found in database; auto-dismissed"
})
auth_db.commit()
kept_count += 1 # Count as kept since we couldn't remove it
removed_count += 1
continue
# Delete tag linkages for this photo
@@ -284,3 +296,72 @@ def review_reported_photos(
errors=errors
)
@router.post("/cleanup", response_model=CleanupResponse)
def cleanup_reported_photos(
current_admin: Annotated[dict, Depends(get_current_admin_user)],
status_filter: Annotated[
Optional[str],
Query(description="Use 'keep' to clear reviewed or 'remove' to clear dismissed records.")
] = None,
auth_db: Session = Depends(get_auth_db),
) -> CleanupResponse:
"""Delete rows from inappropriate_photo_reports based on review status."""
status_mapping = {
"keep": "reviewed",
"remove": "dismissed",
}
if status_filter and status_filter not in status_mapping:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid status_filter. Use 'keep', 'remove', or omit the parameter.",
)
db_status_filter = status_mapping.get(status_filter)
warnings: list[str] = []
try:
if db_status_filter:
result = auth_db.execute(
text(
"""
DELETE FROM inappropriate_photo_reports
WHERE status = :status_filter
"""
),
{"status_filter": db_status_filter},
)
else:
result = auth_db.execute(
text(
"""
DELETE FROM inappropriate_photo_reports
WHERE status IN ('reviewed', 'dismissed')
"""
)
)
deleted_records = result.rowcount if hasattr(result, "rowcount") else 0
auth_db.commit()
if deleted_records == 0:
if db_status_filter:
warnings.append(
f"No reported photos matched the '{status_filter}' decision filter."
)
else:
warnings.append("No reviewed or dismissed reported photos to delete.")
return CleanupResponse(
deleted_records=deleted_records,
errors=[],
warnings=warnings,
)
except Exception as exc:
auth_db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to cleanup reported photos: {exc}",
)