updates again
This commit is contained in:
@@ -0,0 +1,218 @@
|
|||||||
|
# NFOGuard API Endpoints
|
||||||
|
|
||||||
|
## Health & Status Endpoints
|
||||||
|
|
||||||
|
### GET `/health`
|
||||||
|
Health check endpoint with comprehensive status information.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"version": "0.5.0",
|
||||||
|
"uptime": "0:15:23.456789",
|
||||||
|
"database_status": "healthy",
|
||||||
|
"radarr_database": {
|
||||||
|
"status": "healthy",
|
||||||
|
"database_type": "postgresql",
|
||||||
|
"connection": "readable",
|
||||||
|
"readable": true,
|
||||||
|
"tables_exist": true,
|
||||||
|
"sample_data": true,
|
||||||
|
"functional": true,
|
||||||
|
"movie_count": 1250,
|
||||||
|
"history_count": 5847,
|
||||||
|
"movies_with_imdb": 1248,
|
||||||
|
"connection_info": {
|
||||||
|
"type": "postgresql",
|
||||||
|
"host": "postgres",
|
||||||
|
"port": 5432,
|
||||||
|
"database": "radarr-main"
|
||||||
|
},
|
||||||
|
"tested_at": "2025-09-08T17:30:15+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET `/stats`
|
||||||
|
Database statistics for NFOGuard's internal database.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET `/batch/status`
|
||||||
|
Current batch processing queue status.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/batch/status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debug & Testing Endpoints
|
||||||
|
|
||||||
|
### GET `/debug/movie/{imdb_id}`
|
||||||
|
**Primary testing endpoint** - Debug movie import date detection for a specific IMDb ID.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Test with your existing command
|
||||||
|
curl http://localhost:8080/debug/movie/tt1674782
|
||||||
|
|
||||||
|
# Test other movies
|
||||||
|
curl http://localhost:8080/debug/movie/tt1596343 # Fast & Furious 6
|
||||||
|
curl http://localhost:8080/debug/movie/tt0468569 # The Dark Knight
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response includes:**
|
||||||
|
- Movie lookup results (database vs API)
|
||||||
|
- Import date detection with performance metrics
|
||||||
|
- Source information (radarr:db.history.import vs radarr:api.history.import)
|
||||||
|
- Fallback chain details
|
||||||
|
|
||||||
|
### GET `/debug/movie/{imdb_id}/history`
|
||||||
|
Detailed history analysis for a movie (if available in your version).
|
||||||
|
|
||||||
|
## Webhook Endpoints
|
||||||
|
|
||||||
|
### POST `/webhook/radarr`
|
||||||
|
Radarr webhook endpoint for real-time processing.
|
||||||
|
|
||||||
|
**Test webhook manually:**
|
||||||
|
```bash
|
||||||
|
# Simulate a Radarr Download event
|
||||||
|
curl -X POST http://localhost:8080/webhook/radarr \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"eventType": "Download",
|
||||||
|
"movie": {
|
||||||
|
"id": 123,
|
||||||
|
"imdbId": "tt1674782",
|
||||||
|
"title": "The Hobbit: An Unexpected Journey",
|
||||||
|
"year": 2012
|
||||||
|
},
|
||||||
|
"movieFile": {
|
||||||
|
"path": "/movies/The Hobbit An Unexpected Journey (2012) [imdb-tt1674782]/The Hobbit An Unexpected Journey (2012).mkv"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST `/webhook/sonarr`
|
||||||
|
Sonarr webhook endpoint for TV shows.
|
||||||
|
|
||||||
|
**Test webhook manually:**
|
||||||
|
```bash
|
||||||
|
# Simulate a Sonarr Download event
|
||||||
|
curl -X POST http://localhost:8080/webhook/sonarr \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"eventType": "Download",
|
||||||
|
"series": {
|
||||||
|
"imdbId": "tt0944947",
|
||||||
|
"title": "Game of Thrones"
|
||||||
|
},
|
||||||
|
"episodes": [{
|
||||||
|
"seasonNumber": 1,
|
||||||
|
"episodeNumber": 1
|
||||||
|
}]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Processing Endpoints
|
||||||
|
|
||||||
|
### POST `/manual/scan`
|
||||||
|
Trigger manual library scans.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Scan everything
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?scan_type=both"
|
||||||
|
|
||||||
|
# Scan only movies
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?scan_type=movies"
|
||||||
|
|
||||||
|
# Scan only TV shows
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?scan_type=tv"
|
||||||
|
|
||||||
|
# Scan specific path
|
||||||
|
curl -X POST "http://localhost:8080/manual/scan?path=/media/movies"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Database Performance
|
||||||
|
|
||||||
|
### Run Performance Tests
|
||||||
|
Use the included test script to compare database vs API performance:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Inside NFOGuard container or with proper environment
|
||||||
|
python test_db_performance.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Setup for Testing
|
||||||
|
|
||||||
|
**For PostgreSQL (Recommended):**
|
||||||
|
```env
|
||||||
|
RADARR_DB_TYPE=postgresql
|
||||||
|
RADARR_DB_HOST=postgres_host
|
||||||
|
RADARR_DB_PORT=5432
|
||||||
|
RADARR_DB_NAME=radarr-main
|
||||||
|
RADARR_DB_USER=postgres
|
||||||
|
RADARR_DB_PASSWORD=your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
**For SQLite:**
|
||||||
|
```env
|
||||||
|
RADARR_DB_TYPE=sqlite
|
||||||
|
RADARR_DB_PATH=/config/radarr.db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Testing Workflow
|
||||||
|
|
||||||
|
1. **Test your existing command** (should now use database if configured):
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/debug/movie/tt1674782
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check health** to verify database connectivity:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run performance comparison**:
|
||||||
|
```bash
|
||||||
|
python test_db_performance.py
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Test webhook simulation** with a movie in your library:
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/webhook/radarr \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"eventType": "Download",
|
||||||
|
"movie": {"imdbId": "tt1674782", "title": "Test Movie"},
|
||||||
|
"movieFile": {"path": "/path/to/movie.mkv"}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Performance Improvements
|
||||||
|
|
||||||
|
With database access enabled, you should see:
|
||||||
|
- **Movie lookups**: 5-10x faster
|
||||||
|
- **Import date detection**: 10-20x faster
|
||||||
|
- **Bulk operations**: 50x+ faster
|
||||||
|
- **Reduced API calls**: 90%+ reduction
|
||||||
|
- **Log output**: Much cleaner, fewer "pages and pages" messages
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If database access fails, NFOGuard automatically falls back to API methods. Check the logs for:
|
||||||
|
- `"Enhanced performance mode: Direct database access enabled"` (success)
|
||||||
|
- `"Failed to initialize database client, using API fallback"` (fallback)
|
||||||
|
|
||||||
|
The `/health` endpoint will show detailed database status and any connection issues.
|
||||||
@@ -411,6 +411,141 @@ class RadarrDbClient:
|
|||||||
stats["error"] = str(e)
|
stats["error"] = str(e)
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
def health_check(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Comprehensive health check for the Radarr database connection
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with health status, connection info, and basic functionality tests
|
||||||
|
"""
|
||||||
|
health = {
|
||||||
|
"status": "healthy",
|
||||||
|
"database_type": self.db_type,
|
||||||
|
"connection": "ok",
|
||||||
|
"readable": False,
|
||||||
|
"writable": False,
|
||||||
|
"tables_exist": False,
|
||||||
|
"sample_data": False,
|
||||||
|
"issues": [],
|
||||||
|
"tested_at": datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test 1: Basic connection
|
||||||
|
with self._get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Test 2: Check if we can read (basic query)
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT 1')
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result and result[0] == 1:
|
||||||
|
health["readable"] = True
|
||||||
|
health["connection"] = "readable"
|
||||||
|
else:
|
||||||
|
health["issues"].append("Basic SELECT query failed")
|
||||||
|
except Exception as e:
|
||||||
|
health["issues"].append(f"Read test failed: {e}")
|
||||||
|
health["status"] = "degraded"
|
||||||
|
|
||||||
|
# Test 3: Check required tables exist
|
||||||
|
required_tables = ["Movies", "MovieMetadata", "History", "MovieFiles"]
|
||||||
|
existing_tables = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.db_type == "postgresql":
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT table_name
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name IN ('Movies', 'MovieMetadata', 'History', 'MovieFiles')
|
||||||
|
""")
|
||||||
|
else: # SQLite
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT name
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE type='table'
|
||||||
|
AND name IN ('Movies', 'MovieMetadata', 'History', 'MovieFiles')
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
existing_tables = [row[0] for row in rows]
|
||||||
|
|
||||||
|
if len(existing_tables) == len(required_tables):
|
||||||
|
health["tables_exist"] = True
|
||||||
|
else:
|
||||||
|
missing = set(required_tables) - set(existing_tables)
|
||||||
|
health["issues"].append(f"Missing tables: {list(missing)}")
|
||||||
|
health["status"] = "degraded"
|
||||||
|
|
||||||
|
health["existing_tables"] = existing_tables
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
health["issues"].append(f"Table check failed: {e}")
|
||||||
|
health["status"] = "degraded"
|
||||||
|
|
||||||
|
# Test 4: Check for sample data
|
||||||
|
if health["tables_exist"]:
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT COUNT(*) FROM "Movies"')
|
||||||
|
movie_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
cursor.execute('SELECT COUNT(*) FROM "History"')
|
||||||
|
history_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if movie_count > 0 and history_count > 0:
|
||||||
|
health["sample_data"] = True
|
||||||
|
health["movie_count"] = movie_count
|
||||||
|
health["history_count"] = history_count
|
||||||
|
else:
|
||||||
|
health["issues"].append(f"Low data counts - Movies: {movie_count}, History: {history_count}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
health["issues"].append(f"Sample data check failed: {e}")
|
||||||
|
|
||||||
|
# Test 5: Test a real query (movie with IMDb lookup)
|
||||||
|
if health["sample_data"]:
|
||||||
|
try:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM "Movies" m
|
||||||
|
JOIN "MovieMetadata" mm ON m."MovieMetadataId" = mm."Id"
|
||||||
|
WHERE mm."ImdbId" IS NOT NULL
|
||||||
|
""")
|
||||||
|
imdb_movies = cursor.fetchone()[0]
|
||||||
|
health["movies_with_imdb"] = imdb_movies
|
||||||
|
|
||||||
|
if imdb_movies > 0:
|
||||||
|
health["functional"] = True
|
||||||
|
else:
|
||||||
|
health["issues"].append("No movies with IMDb IDs found")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
health["issues"].append(f"Functional test failed: {e}")
|
||||||
|
health["status"] = "degraded"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
health["status"] = "error"
|
||||||
|
health["connection"] = "failed"
|
||||||
|
health["issues"].append(f"Connection failed: {e}")
|
||||||
|
_log("ERROR", f"Database health check failed: {e}")
|
||||||
|
|
||||||
|
# Overall status determination
|
||||||
|
if health["issues"]:
|
||||||
|
if health["status"] == "healthy":
|
||||||
|
health["status"] = "degraded"
|
||||||
|
|
||||||
|
# Add connection details (safe info only)
|
||||||
|
health["connection_info"] = {
|
||||||
|
"type": self.db_type,
|
||||||
|
"host": self.db_host if self.db_type == "postgresql" else None,
|
||||||
|
"port": self.db_port if self.db_type == "postgresql" else None,
|
||||||
|
"database": self.db_name if self.db_type == "postgresql" else None,
|
||||||
|
"path": self.db_path if self.db_type == "sqlite" else None
|
||||||
|
}
|
||||||
|
|
||||||
|
return health
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+26
-3
@@ -132,6 +132,7 @@ class HealthResponse(BaseModel):
|
|||||||
version: str
|
version: str
|
||||||
uptime: str
|
uptime: str
|
||||||
database_status: str
|
database_status: str
|
||||||
|
radarr_database: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Core Processing
|
# Core Processing
|
||||||
@@ -769,8 +770,10 @@ async def radarr_webhook(request: Request, background_tasks: BackgroundTasks):
|
|||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health() -> HealthResponse:
|
async def health() -> HealthResponse:
|
||||||
"""Health check endpoint"""
|
"""Health check endpoint with Radarr database status"""
|
||||||
uptime = datetime.now(timezone.utc) - start_time
|
uptime = datetime.now(timezone.utc) - start_time
|
||||||
|
|
||||||
|
# Check NFOGuard database
|
||||||
try:
|
try:
|
||||||
with db.get_connection() as conn:
|
with db.get_connection() as conn:
|
||||||
conn.execute("SELECT 1").fetchone()
|
conn.execute("SELECT 1").fetchone()
|
||||||
@@ -778,11 +781,31 @@ async def health() -> HealthResponse:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
db_status = f"error: {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
|
||||||
|
radarr_client = client_manager.get_radarr_client()
|
||||||
|
if radarr_client and hasattr(radarr_client, 'db_client') and radarr_client.db_client:
|
||||||
|
try:
|
||||||
|
radarr_db_health = radarr_client.db_client.health_check()
|
||||||
|
if radarr_db_health["status"] != "healthy":
|
||||||
|
overall_status = "degraded"
|
||||||
|
except Exception as e:
|
||||||
|
radarr_db_health = {
|
||||||
|
"status": "error",
|
||||||
|
"error": str(e),
|
||||||
|
"tested_at": datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||||
|
}
|
||||||
|
overall_status = "degraded"
|
||||||
|
|
||||||
return HealthResponse(
|
return HealthResponse(
|
||||||
status="healthy" if db_status == "healthy" else "degraded",
|
status=overall_status,
|
||||||
version=version,
|
version=version,
|
||||||
uptime=str(uptime),
|
uptime=str(uptime),
|
||||||
database_status=db_status
|
database_status=db_status,
|
||||||
|
radarr_database=radarr_db_health
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/stats")
|
@app.get("/stats")
|
||||||
|
|||||||
Reference in New Issue
Block a user