feat: add bulk moderation decisions

This commit is contained in:
ik
2026-09-21 20:25:17 +07:00
parent 2fc4b88a80
commit e74929e7f7
3 changed files with 53 additions and 2 deletions
+22 -1
View File
@@ -20,7 +20,7 @@ from rf4_research.media_assets import publish_quality_upgrades, rollback_quality
from ..media_catalog import MEDIA_ROOT, review_assets, review_file
from ..models import CatchReport, CommunityImportRun, DataSource, ExternalObservation, Fish, ModerationEvent, ModerationStatus, OfficialRecordImport, SourceType, Waterbody
from ..public_cache import public_cache
from ..schemas import AdminCatchReportOut, AdminMediaDecision, AdminMediaReviewOut, AdminMediaRollback, AdminModerationHistoryOut, AdminSourceStatusOut, CatchReportCreated, ExternalAliasSuggestionOut, ExternalObservationAction, ExternalObservationDecision, ExternalObservationMapping, ExternalObservationOut, ExternalObservationPublished, ImportRunOut, ModerationUpdate
from ..schemas import AdminCatchReportOut, AdminMediaDecision, AdminMediaReviewOut, AdminMediaRollback, AdminModerationHistoryOut, AdminSourceStatusOut, CatchReportCreated, ExternalAliasSuggestionOut, ExternalObservationAction, ExternalObservationDecision, ExternalObservationMapping, ExternalObservationOut, ExternalObservationPublished, ImportRunOut, ModerationBulkResult, ModerationBulkUpdate, ModerationUpdate
from ..storage import delete_screenshot, signed_screenshot_url
from ..time_utils import aware
@@ -400,6 +400,27 @@ def moderate_report(report_id: UUID, payload: ModerationUpdate, db: Db, moderato
return CatchReportCreated(id=report.id, moderation_status=report.moderation_status.value)
@router.post("/api/v1/admin/catch-reports/bulk", response_model=ModerationBulkResult)
def moderate_reports_bulk(payload: ModerationBulkUpdate, db: Db, moderator: Annotated[str, Depends(_admin)]) -> ModerationBulkResult:
if payload.status == "rejected" and not (payload.reason or "").strip():
raise HTTPException(status_code=422, detail="rejection reason required")
ids = [item.id for item in payload.items]
reports = list(db.scalars(select(CatchReport).where(CatchReport.id.in_(ids)).with_for_update()))
by_id = {report.id: report for report in reports}
if len(by_id) != len(ids) or any(report.source_type != SourceType.user or report.deleted_at is not None or report.moderation_status != ModerationStatus.pending for report in reports):
raise HTTPException(status_code=409, detail="one or more reports are no longer pending")
expected = {item.id: item.expected_version for item in payload.items}
if any(report.moderation_version != expected[report.id] for report in reports):
raise HTTPException(status_code=409, detail="one or more reports changed; reload the queue")
for report in reports:
previous = report.moderation_status
report.moderation_status = ModerationStatus(payload.status)
report.moderation_version += 1
db.add(ModerationEvent(catch_report=report, created_at=datetime.now(timezone.utc), previous_status=previous, new_status=report.moderation_status, moderator=moderator, reason=payload.reason))
db.commit(); public_cache.invalidate()
return ModerationBulkResult(updated=len(reports))
@router.delete("/api/v1/admin/catch-reports/{report_id}", status_code=204, response_class=Response)
def delete_report(report_id: UUID, db: Db, moderator: Annotated[str, Depends(_admin)], expected_version: int = Query(ge=0)) -> Response:
report = db.scalar(select(CatchReport).where(CatchReport.id == report_id).with_for_update())