Files
spotizerr-dev/routes/core/history.py
2025-08-19 09:36:22 -06:00

344 lines
11 KiB
Python

from fastapi import APIRouter, Request, Depends
from fastapi.responses import JSONResponse
import logging
from routes.utils.history_manager import history_manager
from typing import Any, Dict
# Import authentication dependencies
from routes.auth.middleware import require_auth_from_state, User
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/")
@router.get("")
async def get_history(
request: Request, current_user: User = Depends(require_auth_from_state)
):
"""
Retrieve download history with optional filtering and pagination.
Query parameters:
- limit: Maximum number of records (default: 100, max: 500)
- offset: Number of records to skip (default: 0)
- download_type: Filter by type ('track', 'album', 'playlist')
- status: Filter by status ('completed', 'failed', 'skipped', 'in_progress')
"""
try:
# Parse query parameters
limit = min(int(request.query_params.get("limit", 100)), 500) # Cap at 500
offset = max(int(request.query_params.get("offset", 0)), 0)
download_type = request.query_params.get("download_type")
status = request.query_params.get("status")
# Validate download_type if provided
valid_types = ["track", "album", "playlist"]
if download_type and download_type not in valid_types:
return JSONResponse(
content={
"error": f"Invalid download_type. Must be one of: {valid_types}"
},
status_code=400,
)
# Validate status if provided
valid_statuses = ["completed", "failed", "skipped", "in_progress"]
if status and status not in valid_statuses:
return JSONResponse(
content={"error": f"Invalid status. Must be one of: {valid_statuses}"},
status_code=400,
)
# Get history from manager
history = history_manager.get_download_history(
limit=limit, offset=offset, download_type=download_type, status=status
)
# Add pagination info
response_data: Dict[str, Any] = {
"downloads": history,
"pagination": {
"limit": limit,
"offset": offset,
"returned_count": len(history),
},
}
filters: Dict[str, Any] = {}
if download_type:
filters["download_type"] = download_type
if status:
filters["status"] = status
if filters:
response_data["filters"] = filters
return JSONResponse(content=response_data, status_code=200)
except ValueError as e:
return JSONResponse(
content={"error": f"Invalid parameter value: {str(e)}"}, status_code=400
)
except Exception as e:
logger.error(f"Error retrieving download history: {e}", exc_info=True)
return JSONResponse(
content={"error": "Failed to retrieve download history", "details": str(e)},
status_code=500,
)
@router.get("/{task_id}")
async def get_download_by_task_id(
task_id: str, current_user: User = Depends(require_auth_from_state)
):
"""
Retrieve specific download history by task ID.
Args:
task_id: Celery task ID
"""
try:
download = history_manager.get_download_by_task_id(task_id)
if not download:
return JSONResponse(
content={"error": f"Download with task ID '{task_id}' not found"},
status_code=404,
)
return JSONResponse(content=download, status_code=200)
except Exception as e:
logger.error(
f"Error retrieving download for task {task_id}: {e}", exc_info=True
)
return JSONResponse(
content={"error": "Failed to retrieve download", "details": str(e)},
status_code=500,
)
@router.get("/{task_id}/children")
async def get_download_children(
task_id: str, current_user: User = Depends(require_auth_from_state)
):
"""
Retrieve children tracks for an album or playlist download.
Args:
task_id: Celery task ID
"""
try:
# First get the main download to find the children table
download = history_manager.get_download_by_task_id(task_id)
if not download:
return JSONResponse(
content={"error": f"Download with task ID '{task_id}' not found"},
status_code=404,
)
children_table = download.get("children_table")
if not children_table:
return JSONResponse(
content={"error": f"Download '{task_id}' has no children tracks"},
status_code=404,
)
# Get children tracks
children = history_manager.get_children_history(children_table)
response_data = {
"task_id": task_id,
"download_type": download.get("download_type"),
"title": download.get("title"),
"children_table": children_table,
"tracks": children,
"track_count": len(children),
}
return JSONResponse(content=response_data, status_code=200)
except Exception as e:
logger.error(
f"Error retrieving children for task {task_id}: {e}", exc_info=True
)
return JSONResponse(
content={
"error": "Failed to retrieve download children",
"details": str(e),
},
status_code=500,
)
@router.get("/stats")
async def get_download_stats(current_user: User = Depends(require_auth_from_state)):
"""
Get download statistics and summary information.
"""
try:
stats = history_manager.get_download_stats()
return JSONResponse(content=stats, status_code=200)
except Exception as e:
logger.error(f"Error retrieving download stats: {e}", exc_info=True)
return JSONResponse(
content={
"error": "Failed to retrieve download statistics",
"details": str(e),
},
status_code=500,
)
@router.get("/search")
async def search_history(
request: Request, current_user: User = Depends(require_auth_from_state)
):
"""
Search download history by title or artist.
Query parameters:
- q: Search query (required)
- limit: Maximum number of results (default: 50, max: 200)
"""
try:
query = request.query_params.get("q")
if not query:
return JSONResponse(
content={"error": "Missing required parameter: q (search query)"},
status_code=400,
)
limit = min(int(request.query_params.get("limit", 50)), 200) # Cap at 200
# Search history
results = history_manager.search_history(query, limit)
response_data = {
"query": query,
"results": results,
"result_count": len(results),
"limit": limit,
}
return JSONResponse(content=response_data, status_code=200)
except ValueError as e:
return JSONResponse(
content={"error": f"Invalid parameter value: {str(e)}"}, status_code=400
)
except Exception as e:
logger.error(f"Error searching download history: {e}", exc_info=True)
return JSONResponse(
content={"error": "Failed to search download history", "details": str(e)},
status_code=500,
)
@router.get("/recent")
async def get_recent_downloads(
request: Request, current_user: User = Depends(require_auth_from_state)
):
"""
Get most recent downloads.
Query parameters:
- limit: Maximum number of results (default: 20, max: 100)
"""
try:
limit = min(int(request.query_params.get("limit", 20)), 100) # Cap at 100
recent = history_manager.get_recent_downloads(limit)
response_data = {"downloads": recent, "count": len(recent), "limit": limit}
return JSONResponse(content=response_data, status_code=200)
except ValueError as e:
return JSONResponse(
content={"error": f"Invalid parameter value: {str(e)}"}, status_code=400
)
except Exception as e:
logger.error(f"Error retrieving recent downloads: {e}", exc_info=True)
return JSONResponse(
content={"error": "Failed to retrieve recent downloads", "details": str(e)},
status_code=500,
)
@router.get("/failed")
async def get_failed_downloads(
request: Request, current_user: User = Depends(require_auth_from_state)
):
"""
Get failed downloads.
Query parameters:
- limit: Maximum number of results (default: 50, max: 200)
"""
try:
limit = min(int(request.query_params.get("limit", 50)), 200) # Cap at 200
failed = history_manager.get_failed_downloads(limit)
response_data = {"downloads": failed, "count": len(failed), "limit": limit}
return JSONResponse(content=response_data, status_code=200)
except ValueError as e:
return JSONResponse(
content={"error": f"Invalid parameter value: {str(e)}"}, status_code=400
)
except Exception as e:
logger.error(f"Error retrieving failed downloads: {e}", exc_info=True)
return JSONResponse(
content={"error": "Failed to retrieve failed downloads", "details": str(e)},
status_code=500,
)
@router.post("/cleanup")
async def cleanup_old_history(
request: Request, current_user: User = Depends(require_auth_from_state)
):
"""
Clean up old download history.
JSON body:
- days_old: Number of days old to keep (default: 30)
"""
try:
data = (
await request.json()
if request.headers.get("content-type") == "application/json"
else {}
)
days_old = data.get("days_old", 30)
if not isinstance(days_old, int) or days_old <= 0:
return JSONResponse(
content={"error": "days_old must be a positive integer"},
status_code=400,
)
deleted_count = history_manager.clear_old_history(days_old)
response_data = {
"message": "Successfully cleaned up old download history",
"deleted_records": deleted_count,
"days_old": days_old,
}
return JSONResponse(content=response_data, status_code=200)
except Exception as e:
logger.error(f"Error cleaning up old history: {e}", exc_info=True)
return JSONResponse(
content={"error": "Failed to cleanup old history", "details": str(e)},
status_code=500,
)