Merge pull request 'fixes: web interface and movie processing' (#54) from dev into main
Local Docker Build (Main) / build (push) Successful in 4s
Local Docker Build (Main) / deploy (push) Successful in 1s

Reviewed-on: #54
This commit was merged in pull request #54.
This commit is contained in:
2025-10-20 10:27:38 -04:00
11 changed files with 418 additions and 184 deletions
+134 -20
View File
@@ -1,28 +1,142 @@
# NFOGuard Environment Variables # ===========================================
# NFOGuard Configuration - CORRECTED VERSION
# ===========================================
# Main configuration file - safe to share for debugging
# Sensitive data (API keys, passwords) are in .env.secrets
# User and Group IDs # ===========================================
PUID=1000 # MEDIA PATHS (REQUIRED) - FIXED
PGID=1000 # ===========================================
# Container paths (what NFOGuard sees inside container)
MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
TV_PATHS=/media/TV/tv,/media/TV/tv6
# Timezone # Radarr paths (what Radarr sees on the host system)
TZ=UTC RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6
# Media path (for read-only access to scan NFO files) # Sonarr paths (what Sonarr sees on the host system) - FIXED VARIABLE NAME AND PATHS
MEDIA_PATH=/path/to/your/media SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6
# Database settings (if using PostgreSQL) # Download detection paths (for identifying downloads vs existing files)
# DB_USER=nfoguard DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/completed/,/sabnzbd/,/torrents/
# DB_PASSWORD=your_secure_password
# Emby Plugin Deployment - Bind Mount Method (Recommended) # ===========================================
# Set this to the path where Emby plugins are installed on your host # DATABASE CONFIGURATION
# Common paths: # ===========================================
# - /var/lib/emby/plugins (native Emby install) # NFOGuard Database Configuration (PostgreSQL - v2.6+)
# - /path/to/emby/config/plugins (Docker Emby) # NOTE: For new installations, PostgreSQL is strongly recommended
# - /config/plugins (linuxserver.io Emby) # Set these values in .env.secrets for security:
EMBY_PLUGINS_PATH=/path/to/emby/plugins # - DB_PASSWORD=your_secure_password
# - DB_USER=nfoguard (default)
# NFOGuard specific settings # PostgreSQL Database Settings
DB_TYPE=postgresql
DB_HOST=nfoguard-db # Container name from docker-compose
DB_PORT=5432
DB_NAME=nfoguard
# Legacy SQLite Configuration (Deprecated in v2.6+)
# Only use for existing SQLite installations
# DB_PATH=/app/data/media_dates.db
# Log file directory
LOG_DIR=/app/data/logs
# ===========================================
# RADARR DATABASE CONNECTION (RECOMMENDED)
# ===========================================
# Direct database access for better performance
RADARR_DB_TYPE=postgresql
RADARR_DB_HOST=192.168.255.50
RADARR_DB_PORT=5432
RADARR_DB_NAME=radarr-main
RADARR_DB_USER=postgres
# ===========================================
# API CONNECTIONS (OPTIONAL)
# ===========================================
# API keys are stored in .env.secrets for security
RADARR_URL=http://radarr:7878
SONARR_URL=http://sonarr:8989
JELLYSEERR_URL=http://jellyseerr:5055
# ===========================================
# RELEASE DATE PROCESSING
# ===========================================
# Priority order for release date fallbacks (digital, physical, theatrical)
RELEASE_DATE_PRIORITY=digital,physical,theatrical
#RELEASE_DATE_PRIORITY=digital,theatrical,physical
ENABLE_SMART_DATE_VALIDATION=true
MAX_RELEASE_DATE_GAP_YEARS=10
# Prefer API release dates over file modification dates for manual imports
PREFER_RELEASE_DATES_OVER_FILE_DATES=true
# Disable file date fallback completely (recommended for clean imports)
ALLOW_FILE_DATE_FALLBACK=false
# TMDB country for regional release date preferences
TMDB_COUNTRY=US
# ===========================================
# NFO FILE MANAGEMENT
# ===========================================
# Create/update .nfo files
MANAGE_NFO=true
# Update file modification times to match import dates
FIX_DIR_MTIMES=true
# Add lockdata tags to prevent metadata overwrites
LOCK_METADATA=true
# Brand name in NFO comments
MANAGER_BRAND=NFOGuard
# ===========================================
# PROCESSING BEHAVIOR
# ===========================================
# Movie date update strategy
MOVIE_DATE_UPDATE_MODE=overwrite
MOVIE_PRESERVE_EXISTING=false
# When to update existing dates (always, missing_only, never)
UPDATE_MODE=always
# File modification time behavior (update, leave_alone)
MTIME_BEHAVIOR=update
# Manual scan priority: use existing NFO dates for speed vs check APIs for accuracy
# false (default) = Check external APIs first, use NFO as fallback (slower but accurate)
# true = Use NFO dates immediately without API checks (faster but may use wrong dates)
MANUAL_SCAN_PRIORITIZE_NFO=false
# ===========================================
# PERFORMANCE & BATCHING
# ===========================================
# Delay before processing batched events (seconds)
BATCH_DELAY=5.0
# Maximum concurrent series processing
MAX_CONCURRENT_SERIES=3
# API timeout in seconds
TIMEOUT_SECONDS=45
# ===========================================
# DEBUGGING
# ===========================================
# Enable verbose logging (true/false)
DEBUG=false DEBUG=false
# Enable path mapping debug output (true/false)
PATH_DEBUG=false PATH_DEBUG=false
SUPPRESS_TVDB_WARNINGS=true
# Suppress TVDB API warnings (true/false) - TVDB failures are common and non-critical
SUPPRESS_TVDB_WARNINGS=true
# ===========================================
# SERVER CONFIGURATION
# ===========================================
# Port for webhook server
PORT=8080
+10 -2
View File
@@ -4,9 +4,17 @@
# Add .env.secrets to your .gitignore # Add .env.secrets to your .gitignore
# =========================================== # ===========================================
# RADARR DATABASE CREDENTIALS # NFOGUARD DATABASE CREDENTIALS (REQUIRED)
# =========================================== # ===========================================
# Database password for PostgreSQL connection # NFOGuard PostgreSQL database password (REQUIRED for v2.6+)
DB_PASSWORD=your_secure_nfoguard_password
# Optional: Override default database user (defaults to 'nfoguard')
# DB_USER=nfoguard
# ===========================================
# RADARR DATABASE CREDENTIALS (OPTIONAL)
# ===========================================
# Database password for external Radarr PostgreSQL connection (optional optimization)
RADARR_DB_PASSWORD=your_radarr_db_password RADARR_DB_PASSWORD=your_radarr_db_password
# =========================================== # ===========================================
-129
View File
@@ -1,129 +0,0 @@
# ===========================================
# NFOGuard Configuration - CORRECTED VERSION
# ===========================================
# Main configuration file - safe to share for debugging
# Sensitive data (API keys, passwords) are in .env.secrets
# ===========================================
# MEDIA PATHS (REQUIRED) - FIXED
# ===========================================
# Container paths (what NFOGuard sees inside container)
MOVIE_PATHS=/media/Movies/movies,/media/Movies/movies6
TV_PATHS=/media/TV/tv,/media/TV/tv6
# Radarr paths (what Radarr sees on the host system)
RADARR_ROOT_FOLDERS=/mnt/unionfs/Media/Movies/movies,/mnt/unionfs/Media/Movies/movies6
# Sonarr paths (what Sonarr sees on the host system) - FIXED VARIABLE NAME AND PATHS
SONARR_ROOT_FOLDERS=/mnt/unionfs/Media/TV/tv,/mnt/unionfs/Media/TV/tv6
# Download detection paths (for identifying downloads vs existing files)
DOWNLOAD_PATH_INDICATORS=/downloads/,/nzbget/,/completed/,/sabnzbd/,/torrents/
# ===========================================
# DATABASE CONFIGURATION
# ===========================================
# NFOGuard SQLite database location
DB_PATH=/app/data/media_dates.db
# Log file directory
LOG_DIR=/app/data/logs
# ===========================================
# RADARR DATABASE CONNECTION (RECOMMENDED)
# ===========================================
# Direct database access for better performance
RADARR_DB_TYPE=postgresql
RADARR_DB_HOST=192.168.255.50
RADARR_DB_PORT=5432
RADARR_DB_NAME=radarr-main
RADARR_DB_USER=postgres
# ===========================================
# API CONNECTIONS (OPTIONAL)
# ===========================================
# API keys are stored in .env.secrets for security
RADARR_URL=http://radarr:7878
SONARR_URL=http://sonarr:8989
JELLYSEERR_URL=http://jellyseerr:5055
# ===========================================
# RELEASE DATE PROCESSING
# ===========================================
# Priority order for release date fallbacks (digital, physical, theatrical)
RELEASE_DATE_PRIORITY=digital,physical,theatrical
#RELEASE_DATE_PRIORITY=digital,theatrical,physical
ENABLE_SMART_DATE_VALIDATION=true
MAX_RELEASE_DATE_GAP_YEARS=10
# Prefer API release dates over file modification dates for manual imports
PREFER_RELEASE_DATES_OVER_FILE_DATES=true
# Disable file date fallback completely (recommended for clean imports)
ALLOW_FILE_DATE_FALLBACK=false
# TMDB country for regional release date preferences
TMDB_COUNTRY=US
# ===========================================
# NFO FILE MANAGEMENT
# ===========================================
# Create/update .nfo files
MANAGE_NFO=true
# Update file modification times to match import dates
FIX_DIR_MTIMES=true
# Add lockdata tags to prevent metadata overwrites
LOCK_METADATA=true
# Brand name in NFO comments
MANAGER_BRAND=NFOGuard
# ===========================================
# PROCESSING BEHAVIOR
# ===========================================
# Movie date update strategy
MOVIE_DATE_UPDATE_MODE=overwrite
MOVIE_PRESERVE_EXISTING=false
# When to update existing dates (always, missing_only, never)
UPDATE_MODE=always
# File modification time behavior (update, leave_alone)
MTIME_BEHAVIOR=update
# Manual scan priority: use existing NFO dates for speed vs check APIs for accuracy
# false (default) = Check external APIs first, use NFO as fallback (slower but accurate)
# true = Use NFO dates immediately without API checks (faster but may use wrong dates)
MANUAL_SCAN_PRIORITIZE_NFO=false
# ===========================================
# PERFORMANCE & BATCHING
# ===========================================
# Delay before processing batched events (seconds)
BATCH_DELAY=5.0
# Maximum concurrent series processing
MAX_CONCURRENT_SERIES=3
# API timeout in seconds
TIMEOUT_SECONDS=45
# ===========================================
# DEBUGGING
# ===========================================
# Enable verbose logging (true/false)
DEBUG=false
# Enable path mapping debug output (true/false)
PATH_DEBUG=false
# Suppress TVDB API warnings (true/false) - TVDB failures are common and non-critical
SUPPRESS_TVDB_WARNINGS=true
# ===========================================
# SERVER CONFIGURATION
# ===========================================
# Port for webhook server
PORT=8080
Binary file not shown.
+23 -22
View File
@@ -24,41 +24,42 @@
NFOGuard automatically updates movie and TV show NFO files with proper release dates and metadata when triggered by Radarr/Sonarr webhooks. It preserves existing metadata while adding clean, accurate date information at the bottom of NFO files. NFOGuard automatically updates movie and TV show NFO files with proper release dates and metadata when triggered by Radarr/Sonarr webhooks. It preserves existing metadata while adding clean, accurate date information at the bottom of NFO files.
## Features ## Features
### **🎬 Core Media Management** ### **Core Media Management**
- **Movie & TV Support** - Works with both Radarr and Sonarr - **Movie & TV Support** - Works with both Radarr and Sonarr
- **Smart Date Handling** - Prioritizes digital, physical, and theatrical release dates - **Smart Date Handling** - Prioritizes digital, physical, and theatrical release dates
- **Webhook Integration** - Triggers automatically on import, upgrade, and rename - **Webhook Integration** - Triggers automatically on import, upgrade, and rename
- **NFO Preservation** - Maintains existing metadata, adds fields cleanly at bottom - **NFO Preservation** - Maintains existing metadata, adds fields cleanly at bottom
- **Metadata Locking** - Prevents overwrites with lockdata tags - **Metadata Locking** - Prevents overwrites with lockdata tags
### **Performance & Scalability** ### **Performance & Scalability**
- **Async I/O Operations** - High-performance concurrent file processing - **Async I/O Operations** - High-performance concurrent file processing
- **Batch Processing** - Efficient handling of multiple files simultaneously - **Batch Processing** - Efficient handling of multiple files simultaneously
- **Database Integration** - Direct PostgreSQL/SQLite access for better performance - **PostgreSQL Database** - Production-ready database with optimized queries
- **Smart Concurrency** - Controlled parallel processing with rate limiting - **Smart Skip Logic** - Database-first checking eliminates expensive filesystem scans
- **88% Scan Optimization** - TV library scans reduced from hours to minutes
### **🔧 Configuration & Validation** ### **Web Interface & Management**
- **Complete Web UI** - Episode and movie management with filtering and search
- **Database Cleanup Tools** - Delete orphaned episodes with confirmation dialogs
- **Real-time Statistics** - Live episode counts and source mapping
- **Manual Scan Control** - Smart, full, and incomplete scan modes
- **Health Monitoring** - System status and performance metrics
### **Configuration & Validation**
- **Comprehensive Config Validation** - Validates all settings before startup - **Comprehensive Config Validation** - Validates all settings before startup
- **Runtime Health Checks** - Monitors system health and dependencies - **Runtime Health Checks** - Monitors system health and dependencies
- **Path Validation** - Ensures media directories are accessible - **Path Validation** - Ensures media directories are accessible
- **Database Connectivity Tests** - Validates database connections - **Database Connectivity Tests** - Validates database connections
### **📊 Monitoring & Observability** ### **Production Ready**
- **Health Check APIs** - `/api/v1/health`, `/api/v1/health/ready`, `/api/v1/health/live`
- **Prometheus Metrics** - `/api/v1/metrics` endpoint for monitoring integration
- **Performance Monitoring** - Operation timing, error rates, and bottleneck detection
- **Structured Logging** - JSON logs with correlation IDs for request tracing
- **System Status APIs** - Real-time performance and error metrics
### **🐳 Production Ready**
- **Docker & Kubernetes** - Health checks for orchestration platforms - **Docker & Kubernetes** - Health checks for orchestration platforms
- **Grafana Compatible** - Metrics endpoints for dashboard integration - **Graceful Shutdown** - Proper signal handling for container management
- **Configuration CLI** - Validation tools for troubleshooting - **Configuration CLI** - Validation tools for troubleshooting
- **Modular Architecture** - Clean separation of concerns for maintainability - **Modular Architecture** - Clean separation of concerns for maintainability
## 🚀 Quick Start ## Quick Start
### 1. Download Configuration Files ### 1. Download Configuration Files
@@ -101,7 +102,7 @@ docker-compose logs -f nfoguard
curl http://localhost:8080/health curl http://localhost:8080/health
``` ```
## ⚙️ Configuration ## Configuration
### Environment Files ### Environment Files
@@ -134,7 +135,7 @@ DEBUG=false # Clean production logs
SUPPRESS_TVDB_WARNINGS=true # Hide non-critical API failures SUPPRESS_TVDB_WARNINGS=true # Hide non-critical API failures
``` ```
## 🐳 Docker Images ## Docker Images
### Production (Stable) ### Production (Stable)
```yaml ```yaml
@@ -257,7 +258,7 @@ volumes:
- /home/user/media/tv:/media/TV/tv:rw - /home/user/media/tv:/media/TV/tv:rw
``` ```
## 🔧 Troubleshooting ## Troubleshooting
### Check Logs ### Check Logs
```bash ```bash
@@ -276,7 +277,7 @@ PATH_DEBUG=true
curl http://localhost:8080/health curl http://localhost:8080/health
``` ```
## 📊 Monitoring & Observability ## Monitoring & Observability
NFOGuard provides comprehensive monitoring capabilities for production deployments: NFOGuard provides comprehensive monitoring capabilities for production deployments:
@@ -356,7 +357,7 @@ LOG_LEVEL=INFO
3. **Webhooks**: Check URLs and ensure port 8080 is accessible 3. **Webhooks**: Check URLs and ensure port 8080 is accessible
4. **Database**: Verify PostgreSQL credentials in `.env.secrets` 4. **Database**: Verify PostgreSQL credentials in `.env.secrets`
## 📊 What NFOGuard Does ## What NFOGuard Does
### Before ### Before
```xml ```xml
@@ -392,7 +393,7 @@ LOG_LEVEL=INFO
NFOGuard identifies movies and TV shows using two methods: directory names with IMDb IDs (primary) or NFO files with IMDb IDs (fallback). Your media should follow these conventions: NFOGuard identifies movies and TV shows using two methods: directory names with IMDb IDs (primary) or NFO files with IMDb IDs (fallback). Your media should follow these conventions:
### 🎬 **Movies** ### **Movies**
**Directory Structure:** **Directory Structure:**
``` ```
+1 -1
View File
@@ -1 +1 @@
2.6.5 2.6.6
+61
View File
@@ -688,6 +688,9 @@ async def manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = N
movie_skipped += 1 movie_skipped += 1
elif result == "processed": elif result == "processed":
movie_processed += 1 movie_processed += 1
elif result == "no_video_files":
print(f"INFO: Skipped empty directory: {item.name}")
movie_skipped += 1
except Exception as e: except Exception as e:
print(f"ERROR: Failed processing movie {item}: {e}") print(f"ERROR: Failed processing movie {item}: {e}")
movie_total += 1 movie_total += 1
@@ -1132,6 +1135,34 @@ async def delete_series_episodes(imdb_id: str, dependencies: dict):
} }
async def delete_movie(imdb_id: str, dependencies: dict):
"""Delete a specific movie from the database"""
db = dependencies["db"]
try:
deleted = db.delete_movie(imdb_id)
if deleted:
return {
"success": True,
"message": f"Deleted movie {imdb_id} from database",
"imdb_id": imdb_id
}
else:
return {
"success": False,
"message": f"Movie {imdb_id} not found in database",
"imdb_id": imdb_id
}
except Exception as e:
return {
"success": False,
"error": str(e),
"imdb_id": imdb_id
}
async def cleanup_orphaned_episodes(dependencies: dict): async def cleanup_orphaned_episodes(dependencies: dict):
"""Find and delete episodes that don't have corresponding video files""" """Find and delete episodes that don't have corresponding video files"""
db = dependencies["db"] db = dependencies["db"]
@@ -1154,6 +1185,28 @@ async def cleanup_orphaned_episodes(dependencies: dict):
} }
async def cleanup_orphaned_movies(dependencies: dict):
"""Find and delete movies that don't have corresponding video files"""
db = dependencies["db"]
try:
deleted_movies = db.delete_orphaned_movies()
return {
"success": True,
"message": f"Cleaned up {len(deleted_movies)} orphaned movies",
"deleted_count": len(deleted_movies),
"deleted_movies": deleted_movies
}
except Exception as e:
return {
"success": False,
"error": str(e),
"message": "Failed to cleanup orphaned movies"
}
# --------------------------- # ---------------------------
# Route Registration # Route Registration
# --------------------------- # ---------------------------
@@ -1212,10 +1265,18 @@ def register_routes(app, dependencies: dict):
async def _delete_series_episodes(imdb_id: str): async def _delete_series_episodes(imdb_id: str):
return await delete_series_episodes(imdb_id, dependencies) return await delete_series_episodes(imdb_id, dependencies)
@app.delete("/database/movie/{imdb_id}")
async def _delete_movie(imdb_id: str):
return await delete_movie(imdb_id, dependencies)
@app.post("/database/cleanup/orphaned-episodes") @app.post("/database/cleanup/orphaned-episodes")
async def _cleanup_orphaned_episodes(): async def _cleanup_orphaned_episodes():
return await cleanup_orphaned_episodes(dependencies) return await cleanup_orphaned_episodes(dependencies)
@app.post("/database/cleanup/orphaned-movies")
async def _cleanup_orphaned_movies():
return await cleanup_orphaned_movies(dependencies)
@app.post("/manual/scan") @app.post("/manual/scan")
async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"): async def _manual_scan(background_tasks: BackgroundTasks, path: Optional[str] = None, scan_type: str = "both", scan_mode: str = "smart"):
return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies) return await manual_scan(background_tasks, path, scan_type, scan_mode, dependencies)
+91 -1
View File
@@ -205,7 +205,7 @@ class NFOGuardDatabase:
# Debug: Check what was actually saved # Debug: Check what was actually saved
cursor.execute("SELECT dateadded, source FROM movies WHERE imdb_id = %s", (imdb_id,)) cursor.execute("SELECT dateadded, source FROM movies WHERE imdb_id = %s", (imdb_id,))
result = cursor.fetchone() result = cursor.fetchone()
print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result[0] if result else 'NOT_FOUND'}, source={result[1] if result else 'NOT_FOUND'}") print(f"🔍 DATABASE VERIFY: After upsert, found dateadded={result['dateadded'] if result else 'NOT_FOUND'}, source={result['source'] if result else 'NOT_FOUND'}")
def get_series_episodes(self, imdb_id: str, has_video_file_only: bool = False) -> List[Dict]: def get_series_episodes(self, imdb_id: str, has_video_file_only: bool = False) -> List[Dict]:
"""Get all episodes for a series""" """Get all episodes for a series"""
@@ -411,6 +411,96 @@ class NFOGuardDatabase:
return deleted_episodes return deleted_episodes
def delete_movie(self, imdb_id: str) -> bool:
"""
Delete a specific movie from the database
Args:
imdb_id: Movie IMDb ID
Returns:
True if movie was deleted, False if not found
"""
with self.get_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
DELETE FROM movies
WHERE imdb_id = %s
""", (imdb_id,))
deleted_count = cursor.rowcount
conn.commit()
return deleted_count > 0
def delete_orphaned_movies(self) -> List[Dict]:
"""
Find and delete movies that don't have corresponding video files on disk
This requires checking filesystem for each movie, so use carefully
Returns:
List of deleted movies with their details
"""
from pathlib import Path
deleted_movies = []
with self.get_connection() as conn:
cursor = conn.cursor()
# Get all movies with their paths
cursor.execute("""
SELECT imdb_id, path, dateadded, source
FROM movies
""")
movies_list = cursor.fetchall()
for movie in movies_list:
imdb_id = movie['imdb_id']
movie_path = Path(movie['path'])
if not movie_path.exists():
# Movie directory doesn't exist - delete it
cursor.execute("""
DELETE FROM movies
WHERE imdb_id = %s
""", (imdb_id,))
deleted_movies.append({
'imdb_id': imdb_id,
'reason': 'directory_not_found',
'path': str(movie_path),
'dateadded': movie['dateadded'],
'source': movie['source']
})
continue
# Check for video files
video_exts = (".mkv", ".mp4", ".avi", ".mov", ".m4v")
has_video = any(f.is_file() and f.suffix.lower() in video_exts
for f in movie_path.iterdir() if f.is_file())
if not has_video:
# No video files found - delete this movie
cursor.execute("""
DELETE FROM movies
WHERE imdb_id = %s
""", (imdb_id,))
deleted_movies.append({
'imdb_id': imdb_id,
'reason': 'no_video_files',
'path': str(movie_path),
'dateadded': movie['dateadded'],
'source': movie['source']
})
conn.commit()
return deleted_movies
def close(self): def close(self):
"""Close all database connections""" """Close all database connections"""
if hasattr(self._local, 'connection'): if hasattr(self._local, 'connection'):
+59 -6
View File
@@ -6,13 +6,17 @@ services:
image: sbcrumb/nfoguard:latest image: sbcrumb/nfoguard:latest
# Alternative: Use specific version # Alternative: Use specific version
# image: sbcrumb/nfoguard:v1.5.5 # image: sbcrumb/nfoguard:v2.6.5
# Alternative: Use development version # Alternative: Use development version
# image: sbcrumb/nfoguard:dev # image: sbcrumb/nfoguard:dev
container_name: nfoguard container_name: nfoguard
# Database dependency
depends_on:
- nfoguard-postgres
# Restart policy # Restart policy
restart: unless-stopped restart: unless-stopped
@@ -87,6 +91,54 @@ services:
max-size: "10m" max-size: "10m"
max-file: "3" max-file: "3"
# PostgreSQL Database (Required for v2.6+)
nfoguard-postgres:
image: postgres:15-alpine
container_name: nfoguard-db
# Restart policy
restart: unless-stopped
# Environment variables for PostgreSQL
environment:
- POSTGRES_DB=nfoguard
- POSTGRES_USER=nfoguard
- POSTGRES_PASSWORD=${DB_PASSWORD} # Set in .env.secrets
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
# PostgreSQL data persistence
volumes:
- ./postgres-data:/var/lib/postgresql/data
# PostgreSQL port (optional - only needed for external access)
# ports:
# - "5432:5432"
# Health check for database
healthcheck:
test: ["CMD-SHELL", "pg_isready -U nfoguard -d nfoguard"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# Resource limits for database
deploy:
resources:
limits:
memory: 256M
cpus: '0.3'
reservations:
memory: 128M
cpus: '0.1'
# Logging configuration
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Optional: Custom network for media services # Optional: Custom network for media services
# networks: # networks:
# media-network: # media-network:
@@ -97,12 +149,13 @@ services:
# =========================================== # ===========================================
# 1. Copy this file to docker-compose.yml # 1. Copy this file to docker-compose.yml
# 2. Update volume mounts to match your actual media paths # 2. Update volume mounts to match your actual media paths
# 3. Copy .env.template to .env and configure # 3. Copy .env.example to .env and configure all settings
# 4. Copy .env.secrets.template to .env.secrets and add credentials # 4. Copy .env.secrets.example to .env.secrets and add credentials (especially DB_PASSWORD)
# 5. Create data directory: mkdir -p ./data # 5. Create directories: mkdir -p ./data ./postgres-data
# 6. Run: docker-compose up -d # 6. Run: docker-compose up -d
# 7. Check logs: docker-compose logs -f nfoguard # 7. Check logs: docker-compose logs -f
# 8. Access health check: curl http://localhost:8080/health # 8. Access web interface: http://localhost:8080
# 9. Access health check: curl http://localhost:8080/health
# =========================================== # ===========================================
# VOLUME MAPPING EXAMPLES # VOLUME MAPPING EXAMPLES
+2 -3
View File
@@ -188,9 +188,8 @@ class MovieProcessor:
has_video = any(f.is_file() and f.suffix.lower() in video_exts for f in movie_path.iterdir()) has_video = any(f.is_file() and f.suffix.lower() in video_exts for f in movie_path.iterdir())
if not has_video: if not has_video:
_log("WARNING", f"No video files found in: {movie_path}") _log("WARNING", f"No video files found in: {movie_path} - skipping database entry")
self.db.upsert_movie_dates(imdb_id, None, None, None, False) return "no_video_files"
return "processed"
# TIER 1: Check database first (fastest - local lookup) # TIER 1: Check database first (fastest - local lookup)
existing = self.db.get_movie_dates(imdb_id) existing = self.db.get_movie_dates(imdb_id)
+37
View File
@@ -289,6 +289,9 @@ function updateMoviesTable(data) {
<button class="btn btn-sm btn-secondary" onclick="debugMovie('${movie.imdb_id}')" title="Debug Data"> <button class="btn btn-sm btn-secondary" onclick="debugMovie('${movie.imdb_id}')" title="Debug Data">
<i class="fas fa-bug"></i> <i class="fas fa-bug"></i>
</button> </button>
<button class="btn btn-sm btn-danger" onclick="deleteMovie('${movie.imdb_id}')" style="margin-left: 5px;" title="Delete Movie">
<i class="fas fa-trash"></i> Delete
</button>
</td> </td>
</tr> </tr>
`; `;
@@ -1315,6 +1318,40 @@ async function deleteEpisode(imdbId, season, episode) {
} }
} }
// Movie deletion functionality
async function deleteMovie(imdbId) {
// Confirmation dialog
if (!confirm(`⚠️ Delete Movie?\n\nThis will permanently remove the movie (${imdbId}) from the database.\n\nAre you sure you want to continue?`)) {
return;
}
try {
const response = await fetch(`/database/movie/${imdbId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (response.ok && result.success) {
showToast(`✅ Movie deleted successfully`, 'success');
// Refresh the movies table
loadMovies(currentMoviesPage);
} else {
const errorMsg = result.message || result.error || 'Unknown error';
showToast(`❌ Failed to delete movie: ${errorMsg}`, 'error');
}
} catch (error) {
console.error('Delete movie failed:', error);
showToast(`❌ Delete failed: ${error.message}`, 'error');
}
}
// Update episode counts in modal after deletion // Update episode counts in modal after deletion
function updateEpisodeModalCounts() { function updateEpisodeModalCounts() {
const remainingRows = document.querySelectorAll('#episodes-table-body tr'); const remainingRows = document.querySelectorAll('#episodes-table-body tr');