fix: purge tackle provenance on deletion
CI / backend-and-migrations (push) Failing after 37s
CI / dependency-audit (push) Failing after 1s
CI / astro-build (push) Failing after 10m9s
CI / compose-e2e (push) Failing after 14m40s

This commit is contained in:
ik
2026-09-22 19:55:34 +07:00
parent b0c1a05754
commit c46b28d1bc
3 changed files with 18 additions and 11 deletions
+11 -8
View File
@@ -4,8 +4,8 @@ from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Callable
from sqlalchemy import delete, or_, select
from sqlalchemy.orm import Session
from sqlalchemy import delete, select
from sqlalchemy.orm import Session, selectinload
from .models import CatchReport, ExternalObservation, ModerationEvent, ModerationStatus, SourceType, SubmissionAttempt
@@ -42,15 +42,15 @@ def apply_retention(
counts["submission_attempts"] = len(attempts)
candidate_cutoff = current - timedelta(days=min(policy.unreviewed_days, policy.approved_personal_days))
candidates = list(session.scalars(select(CatchReport).where(
candidate_rows = list(session.scalars(select(CatchReport).options(selectinload(CatchReport.tackle_components)).where(
CatchReport.source_type == SourceType.user,
CatchReport.reported_at < candidate_cutoff,
or_(
CatchReport.player_name.is_not(None), CatchReport.source_url.is_not(None),
CatchReport.raw_payload.is_not(None), CatchReport.screenshot_key.is_not(None),
CatchReport.screenshot_upload_token_hash.is_not(None),
),
)))
candidates = [item for item in candidate_rows if (
item.player_name is not None or item.source_url is not None or item.raw_payload is not None
or item.screenshot_key is not None or item.screenshot_upload_token_hash is not None
or any(component.source_url is not None or component.raw_payload is not None for component in item.tackle_components)
)]
reports = [item for item in candidates if item.reported_at.replace(tzinfo=item.reported_at.tzinfo or timezone.utc) < current - timedelta(
days=policy.approved_personal_days if item.moderation_status == ModerationStatus.approved else policy.unreviewed_days,
)]
@@ -96,6 +96,9 @@ def apply_retention(
report.raw_payload = None
report.screenshot_key = None
report.screenshot_upload_token_hash = None
for component in report.tackle_components:
component.source_url = None
component.raw_payload = None
for observation in stale:
session.delete(observation)
for observation in published:
+6 -2
View File
@@ -8,7 +8,7 @@ import httpx
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request, Response
from fastapi.responses import FileResponse, JSONResponse
from sqlalchemy import case, func, or_, select
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import joinedload, selectinload
from ..admin_security import verify_admin
from ..community_review import ExternalReviewError, map_observation, publish_observation, reject_observation, suggest_aliases
@@ -479,7 +479,7 @@ def admin_reports(db: Db, _: Annotated[str, Depends(_admin)], status: Moderation
@router.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.scalar(select(CatchReport).where(CatchReport.id == report_id).with_for_update())
report = db.scalar(select(CatchReport).options(selectinload(CatchReport.tackle_components)).where(CatchReport.id == report_id).with_for_update())
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")
if report.moderation_version != payload.expected_version:
@@ -534,6 +534,10 @@ def delete_report(report_id: UUID, db: Db, moderator: Annotated[str, Depends(_ad
report.source_url = None
report.screenshot_key = None
report.raw_payload = None
report.screenshot_upload_token_hash = None
for component in report.tackle_components:
component.source_url = None
component.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()
public_cache.invalidate()