119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
|
|
from app.api.admin.deps import get_admin_claims
|
|
from app.core.errors import ApiError, ErrorCode
|
|
from app.core.request_id import get_or_create_request_id
|
|
from app.db.request_logs_cleanup import (
|
|
DEFAULT_REQUEST_LOGS_CHUNK_SIZE,
|
|
DEFAULT_REQUEST_LOGS_KEEP_DAYS,
|
|
DEFAULT_REQUEST_LOGS_MAX_DELETE_ROWS,
|
|
cleanup_request_logs,
|
|
preview_request_logs_cleanup,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _parse_bool(value: Any, *, default: bool) -> bool:
|
|
if value is None:
|
|
return default
|
|
if isinstance(value, bool):
|
|
return value
|
|
if isinstance(value, int) and value in (0, 1):
|
|
return bool(value)
|
|
if isinstance(value, str):
|
|
v = value.strip().lower()
|
|
if v in {"1", "true", "yes", "y", "on"}:
|
|
return True
|
|
if v in {"0", "false", "no", "n", "off"}:
|
|
return False
|
|
return default
|
|
|
|
|
|
async def _load_cleanup_request_logs_json(request: Request) -> dict[str, Any]:
|
|
try:
|
|
data = await request.json()
|
|
except Exception as exc:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Invalid JSON body", status_code=400) from exc
|
|
if not isinstance(data, dict):
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Invalid JSON body", status_code=400)
|
|
|
|
keep_days_raw = data.get("keep_days", DEFAULT_REQUEST_LOGS_KEEP_DAYS)
|
|
try:
|
|
keep_days = int(keep_days_raw)
|
|
except Exception as exc:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported keep_days", status_code=400) from exc
|
|
if keep_days < 0 or keep_days > 36500:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported keep_days", status_code=400)
|
|
|
|
max_delete_rows_raw = data.get("max_delete_rows", DEFAULT_REQUEST_LOGS_MAX_DELETE_ROWS)
|
|
try:
|
|
max_delete_rows = int(max_delete_rows_raw)
|
|
except Exception as exc:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported max_delete_rows", status_code=400) from exc
|
|
if max_delete_rows < 1 or max_delete_rows > 10_000_000:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported max_delete_rows", status_code=400)
|
|
|
|
chunk_size_raw = data.get("chunk_size", DEFAULT_REQUEST_LOGS_CHUNK_SIZE)
|
|
try:
|
|
chunk_size = int(chunk_size_raw)
|
|
except Exception as exc:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported chunk_size", status_code=400) from exc
|
|
if chunk_size < 1 or chunk_size > 100_000:
|
|
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported chunk_size", status_code=400)
|
|
|
|
dry_run = _parse_bool(data.get("dry_run"), default=False)
|
|
|
|
return {
|
|
"keep_days": int(keep_days),
|
|
"max_delete_rows": int(max_delete_rows),
|
|
"chunk_size": int(chunk_size),
|
|
"dry_run": bool(dry_run),
|
|
}
|
|
|
|
|
|
@router.post("/maintenance/request-logs/cleanup")
|
|
async def cleanup_request_logs_endpoint(
|
|
request: Request,
|
|
_claims: dict[str, Any] = Depends(get_admin_claims),
|
|
) -> dict[str, Any]:
|
|
_ = _claims
|
|
rid = get_or_create_request_id(request)
|
|
cfg = await _load_cleanup_request_logs_json(request)
|
|
|
|
engine = request.app.state.engine
|
|
if bool(cfg["dry_run"]):
|
|
preview = await preview_request_logs_cleanup(
|
|
engine,
|
|
keep_days=int(cfg["keep_days"]),
|
|
max_delete_rows=int(cfg["max_delete_rows"]),
|
|
)
|
|
return {
|
|
"ok": True,
|
|
"dry_run": True,
|
|
"cutoff": preview.cutoff,
|
|
"would_delete": int(preview.would_delete),
|
|
"has_more": bool(preview.has_more),
|
|
"request_id": rid,
|
|
}
|
|
|
|
result = await cleanup_request_logs(
|
|
engine,
|
|
keep_days=int(cfg["keep_days"]),
|
|
max_delete_rows=int(cfg["max_delete_rows"]),
|
|
chunk_size=int(cfg["chunk_size"]),
|
|
)
|
|
return {
|
|
"ok": True,
|
|
"dry_run": False,
|
|
"cutoff": result.cutoff,
|
|
"deleted": int(result.deleted),
|
|
"has_more": bool(result.has_more),
|
|
"request_id": rid,
|
|
}
|
|
|