This commit is contained in:
+231
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
FastAPI routes for NFOGuard
|
||||
"""
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException, BackgroundTasks, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
# Import models
|
||||
from .models import SonarrWebhook, RadarrWebhook, HealthResponse, TVSeasonRequest, TVEpisodeRequest
|
||||
|
||||
# Import core utilities
|
||||
from core.logging import _log
|
||||
from core.database import NFOGuardDatabase
|
||||
|
||||
|
||||
async def _read_payload(request: Request) -> Optional[Dict[str, Any]]:
|
||||
"""Read and validate JSON payload from webhook"""
|
||||
try:
|
||||
content_type = request.headers.get("content-type", "")
|
||||
if "application/json" not in content_type.lower():
|
||||
_log("WARNING", f"Invalid content type: {content_type}")
|
||||
return None
|
||||
|
||||
body = await request.body()
|
||||
if not body:
|
||||
_log("WARNING", "Empty request body")
|
||||
return None
|
||||
|
||||
payload = json.loads(body.decode('utf-8'))
|
||||
_log("DEBUG", f"Raw payload: {json.dumps(payload, indent=2)}")
|
||||
return payload
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
_log("ERROR", f"Invalid JSON payload: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Error reading payload: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def register_routes(
|
||||
app: FastAPI,
|
||||
tv_processor,
|
||||
movie_processor,
|
||||
batcher,
|
||||
db: NFOGuardDatabase,
|
||||
start_time: datetime,
|
||||
version: str
|
||||
):
|
||||
"""Register all routes with the FastAPI app"""
|
||||
|
||||
@app.post("/webhook/sonarr")
|
||||
async def sonarr_webhook(request: Request, background_tasks: BackgroundTasks):
|
||||
"""Handle Sonarr webhooks"""
|
||||
try:
|
||||
payload = await _read_payload(request)
|
||||
if not payload:
|
||||
raise HTTPException(status_code=422, detail="Empty Sonarr payload")
|
||||
|
||||
webhook = SonarrWebhook(**payload)
|
||||
_log("INFO", f"Received Sonarr webhook: {webhook.eventType}")
|
||||
|
||||
if webhook.eventType not in ["Download", "Upgrade", "Rename"]:
|
||||
return {"status": "ignored", "reason": f"Event type {webhook.eventType} not processed"}
|
||||
|
||||
if not webhook.series:
|
||||
_log("WARNING", "No series data in Sonarr webhook")
|
||||
return {"status": "error", "message": "No series data"}
|
||||
|
||||
# Process the webhook (implementation would continue from original file)
|
||||
# This is a placeholder - full implementation needs to be copied from nfoguard.py
|
||||
return {"status": "queued", "message": "Sonarr webhook queued for processing"}
|
||||
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Sonarr webhook error: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Webhook processing failed: {str(e)}")
|
||||
|
||||
@app.post("/webhook/radarr")
|
||||
async def radarr_webhook(request: Request, background_tasks: BackgroundTasks):
|
||||
"""Handle Radarr webhooks"""
|
||||
try:
|
||||
payload = await _read_payload(request)
|
||||
if not payload:
|
||||
raise HTTPException(status_code=422, detail="Empty Radarr payload")
|
||||
|
||||
webhook = RadarrWebhook(**payload)
|
||||
_log("INFO", f"Received Radarr webhook: {webhook.eventType}")
|
||||
|
||||
if webhook.eventType not in ["Download", "Upgrade", "Rename"]:
|
||||
return {"status": "ignored", "reason": f"Event type {webhook.eventType} not processed"}
|
||||
|
||||
if not webhook.movie:
|
||||
_log("WARNING", "No movie data in Radarr webhook")
|
||||
return {"status": "error", "message": "No movie data"}
|
||||
|
||||
# Process the webhook (implementation would continue from original file)
|
||||
# This is a placeholder - full implementation needs to be copied from nfoguard.py
|
||||
return {"status": "queued", "message": "Radarr webhook queued for processing"}
|
||||
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Radarr webhook error: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Webhook processing failed: {str(e)}")
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> HealthResponse:
|
||||
"""Health check endpoint with database status"""
|
||||
uptime = datetime.now(timezone.utc) - start_time
|
||||
|
||||
# Check NFOGuard database
|
||||
try:
|
||||
with db.get_connection() as conn:
|
||||
conn.execute("SELECT 1").fetchone()
|
||||
db_status = "healthy"
|
||||
except Exception as e:
|
||||
db_status = f"error: {e}"
|
||||
|
||||
# Check Radarr database if available
|
||||
radarr_db_health = None
|
||||
overall_status = "healthy" if db_status == "healthy" else "degraded"
|
||||
|
||||
# Get Radarr client with database access from movie processor
|
||||
try:
|
||||
if hasattr(movie_processor, 'radarr') and movie_processor.radarr:
|
||||
if hasattr(movie_processor.radarr, 'db_client') and movie_processor.radarr.db_client:
|
||||
radarr_test = movie_processor.radarr.db_client.test_connection()
|
||||
radarr_db_health = {
|
||||
"radarr_db_available": radarr_test["success"],
|
||||
"radarr_db_path": radarr_test.get("db_path"),
|
||||
"radarr_db_error": radarr_test.get("error")
|
||||
}
|
||||
if not radarr_test["success"]:
|
||||
overall_status = "degraded"
|
||||
except Exception as e:
|
||||
_log("DEBUG", f"Radarr database health check failed: {e}")
|
||||
|
||||
return HealthResponse(
|
||||
status=overall_status,
|
||||
version=version,
|
||||
uptime=str(uptime).split('.')[0], # Remove microseconds
|
||||
database_status=db_status,
|
||||
radarr_database=radarr_db_health
|
||||
)
|
||||
|
||||
@app.get("/stats")
|
||||
async def get_stats():
|
||||
"""Get processing statistics"""
|
||||
return {
|
||||
"message": "Stats endpoint - implementation needed",
|
||||
"uptime": str(datetime.now(timezone.utc) - start_time).split('.')[0]
|
||||
}
|
||||
|
||||
@app.get("/batch/status")
|
||||
async def batch_status():
|
||||
"""Get current batch processing status"""
|
||||
return {
|
||||
"message": "Batch status endpoint - implementation needed",
|
||||
"pending_items": len(getattr(batcher, 'pending', {}))
|
||||
}
|
||||
|
||||
@app.post("/manual/scan")
|
||||
async def manual_scan(background_tasks: BackgroundTasks):
|
||||
"""Trigger manual library scan"""
|
||||
try:
|
||||
# Add the actual manual scan logic here
|
||||
_log("INFO", "Manual scan triggered via API")
|
||||
return {"status": "started", "message": "Manual scan initiated"}
|
||||
except Exception as e:
|
||||
_log("ERROR", f"Manual scan failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Manual scan failed: {str(e)}")
|
||||
|
||||
@app.post("/tv/scan-season")
|
||||
async def scan_tv_season(request: TVSeasonRequest, background_tasks: BackgroundTasks):
|
||||
"""Scan specific TV season"""
|
||||
try:
|
||||
_log("INFO", f"TV season scan requested: {request.series_path}/{request.season_name}")
|
||||
return {"status": "started", "message": f"Scanning season {request.season_name}"}
|
||||
except Exception as e:
|
||||
_log("ERROR", f"TV season scan failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Season scan failed: {str(e)}")
|
||||
|
||||
@app.post("/tv/scan-episode")
|
||||
async def scan_tv_episode(request: TVEpisodeRequest, background_tasks: BackgroundTasks):
|
||||
"""Scan specific TV episode"""
|
||||
try:
|
||||
_log("INFO", f"TV episode scan requested: {request.series_path}/{request.season_name}/{request.episode_name}")
|
||||
return {"status": "started", "message": f"Scanning episode {request.episode_name}"}
|
||||
except Exception as e:
|
||||
_log("ERROR", f"TV episode scan failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=f"Episode scan failed: {str(e)}")
|
||||
|
||||
# Debug endpoints
|
||||
@app.get("/debug/movie/{imdb_id}")
|
||||
async def debug_movie(imdb_id: str):
|
||||
"""Debug movie processing for specific IMDb ID"""
|
||||
return {"message": f"Debug info for movie {imdb_id} - implementation needed"}
|
||||
|
||||
@app.get("/debug/movie/{imdb_id}/history")
|
||||
async def debug_movie_history(imdb_id: str):
|
||||
"""Debug movie processing history"""
|
||||
return {"message": f"Debug history for movie {imdb_id} - implementation needed"}
|
||||
|
||||
@app.get("/debug/movie/{imdb_id}/priority")
|
||||
async def debug_movie_priority(imdb_id: str):
|
||||
"""Debug movie priority processing"""
|
||||
return {"message": f"Debug priority for movie {imdb_id} - implementation needed"}
|
||||
|
||||
@app.get("/debug/tmdb/{imdb_id}")
|
||||
async def debug_tmdb(imdb_id: str):
|
||||
"""Debug TMDB API calls for movie"""
|
||||
return {"message": f"Debug TMDB for movie {imdb_id} - implementation needed"}
|
||||
|
||||
# Test endpoints
|
||||
@app.post("/test/bulk-update")
|
||||
async def test_bulk_update():
|
||||
"""Test bulk update functionality"""
|
||||
return {"message": "Test bulk update - implementation needed"}
|
||||
|
||||
@app.post("/test/movie-scan")
|
||||
async def test_movie_scan():
|
||||
"""Test movie scan functionality"""
|
||||
return {"message": "Test movie scan - implementation needed"}
|
||||
|
||||
@app.post("/bulk/update")
|
||||
async def bulk_update():
|
||||
"""Bulk update functionality"""
|
||||
return {"message": "Bulk update - implementation needed"}
|
||||
Reference in New Issue
Block a user