Add audited user report deletion
This commit is contained in:
+26
-4
@@ -17,14 +17,14 @@ from .config import settings
|
||||
from .importer import ImportSourceError, import_records, normalize
|
||||
from .models import Bait, BaitKind, CatchReport, Fish, ModerationEvent, ModerationStatus, OfficialRecordImport, SourceType, Spot, Waterbody
|
||||
from .schemas import ActivityOut, AdminCatchReportOut, BaitOut, CatchOut, CatchReportCreate, CatchReportCreated, FishOut, ImportRunOut, ModerationUpdate, OfficialRecordOut, SpotOut, WaterbodyOut
|
||||
from .storage import ScreenshotError, signed_screenshot_url, upload_screenshot
|
||||
from .storage import ScreenshotError, delete_screenshot, signed_screenshot_url, upload_screenshot
|
||||
|
||||
|
||||
app = FastAPI(title="RF4 Spotter API", version="0.1.0")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:4321", "http://127.0.0.1:4321"],
|
||||
allow_methods=["GET", "PATCH"],
|
||||
allow_methods=["GET", "PATCH", "DELETE"],
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
)
|
||||
Db = Annotated[Session, Depends(get_session)]
|
||||
@@ -190,14 +190,14 @@ def add_screenshot(report_id: UUID, db: Db, screenshot: UploadFile = File()) ->
|
||||
|
||||
@app.get("/api/v1/admin/catch-reports", response_model=list[AdminCatchReportOut])
|
||||
def admin_reports(db: Db, _: Annotated[str, Depends(_admin)], status: ModerationStatus = ModerationStatus.pending, limit: int = Query(50, ge=1, le=100)) -> list[AdminCatchReportOut]:
|
||||
reports = list(db.scalars(select(CatchReport).options(joinedload(CatchReport.fish), joinedload(CatchReport.waterbody), joinedload(CatchReport.spot), joinedload(CatchReport.bait)).where(CatchReport.source_type == SourceType.user, CatchReport.moderation_status == status).order_by(CatchReport.reported_at).limit(limit)))
|
||||
reports = list(db.scalars(select(CatchReport).options(joinedload(CatchReport.fish), joinedload(CatchReport.waterbody), joinedload(CatchReport.spot), joinedload(CatchReport.bait)).where(CatchReport.source_type == SourceType.user, CatchReport.moderation_status == status, CatchReport.deleted_at.is_(None)).order_by(CatchReport.reported_at).limit(limit)))
|
||||
return [AdminCatchReportOut(id=r.id, fish=r.fish.name_ru, waterbody=r.waterbody.name_ru, coordinates=f"{r.spot.x}:{r.spot.y}" if r.spot else "—", weight_g=r.weight_g, bait=r.bait.name if r.bait else None, player_name=r.player_name, reported_at=r.reported_at, moderation_status=r.moderation_status.value, comment=(r.raw_payload or {}).get("comment"), screenshot_url=signed_screenshot_url(r.screenshot_key) if r.screenshot_key else None) for r in reports]
|
||||
|
||||
|
||||
@app.patch("/api/v1/admin/catch-reports/{report_id}", response_model=CatchReportCreated)
|
||||
def moderate_report(report_id: UUID, payload: ModerationUpdate, db: Db, moderator: Annotated[str, Depends(_admin)]) -> CatchReportCreated:
|
||||
report = db.get(CatchReport, report_id)
|
||||
if report is None or report.source_type != SourceType.user:
|
||||
if report is None or report.source_type != SourceType.user or report.deleted_at is not None:
|
||||
raise HTTPException(status_code=404, detail="catch report not found")
|
||||
previous = report.moderation_status
|
||||
report.moderation_status = ModerationStatus(payload.status)
|
||||
@@ -206,6 +206,28 @@ def moderate_report(report_id: UUID, payload: ModerationUpdate, db: Db, moderato
|
||||
return CatchReportCreated(id=report.id, moderation_status=report.moderation_status.value)
|
||||
|
||||
|
||||
@app.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)]) -> Response:
|
||||
report = db.get(CatchReport, report_id)
|
||||
if report is None or report.source_type != SourceType.user or report.deleted_at is not None:
|
||||
raise HTTPException(status_code=404, detail="catch report not found")
|
||||
previous = report.moderation_status
|
||||
if report.screenshot_key:
|
||||
try:
|
||||
delete_screenshot(report.screenshot_key)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail="screenshot deletion failed") from exc
|
||||
report.moderation_status = ModerationStatus.rejected
|
||||
report.deleted_at = datetime.now(timezone.utc)
|
||||
report.player_name = None
|
||||
report.source_url = None
|
||||
report.screenshot_key = None
|
||||
report.raw_payload = None
|
||||
db.add(ModerationEvent(catch_report=report, created_at=report.deleted_at, previous_status=previous, new_status=ModerationStatus.rejected, moderator=moderator, reason="user report deleted and anonymized"))
|
||||
db.commit()
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
def _check_rate_limit(client: str) -> None:
|
||||
now = datetime.now(timezone.utc)
|
||||
recent = _submissions[client]
|
||||
|
||||
Reference in New Issue
Block a user