feat: add bulk moderation decisions
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -326,6 +326,28 @@ class ModerationUpdate(BaseModel):
|
||||
return value
|
||||
|
||||
|
||||
class ModerationBulkItem(BaseModel):
|
||||
id: UUID
|
||||
expected_version: int = Field(ge=0)
|
||||
|
||||
|
||||
class ModerationBulkUpdate(BaseModel):
|
||||
status: str
|
||||
reason: str | None = Field(default=None, max_length=1000)
|
||||
items: list[ModerationBulkItem] = Field(min_length=1, max_length=50)
|
||||
|
||||
@field_validator("status")
|
||||
@classmethod
|
||||
def known_bulk_status(cls, value: str) -> str:
|
||||
if value not in {"approved", "rejected", "pending"}:
|
||||
raise ValueError("unknown moderation status")
|
||||
return value
|
||||
|
||||
|
||||
class ModerationBulkResult(BaseModel):
|
||||
updated: int
|
||||
|
||||
|
||||
class ExternalObservationOut(BaseModel):
|
||||
id: UUID
|
||||
source_system: str
|
||||
|
||||
Reference in New Issue
Block a user