This commit is contained in:
Deployment System
2026-01-21 21:33:32 +02:00
parent 8a89df3486
commit ae3b82862d
4 changed files with 748 additions and 2 deletions

View File

@@ -80,13 +80,13 @@ echo -e "${YELLOW}🔧 [3/4] Updating HTTPS configuration in database...${NC}"
docker compose exec -T digiserver-app python << EOF
from app.app import create_app
from app.models.https_config import HttpsConfig
from app.models.https_config import HTTPSConfig
from app.extensions import db
app = create_app('production')
with app.app_context():
# Update or create HTTPS config for the new IP
https_config = HttpsConfig.query.first()
https_config = HTTPSConfig.query.first()
if https_config:
https_config.hostname = '$HOSTNAME'

View File

@@ -0,0 +1,144 @@
# Edit Media API Troubleshooting Guide
## Issue
Players are trying to send edited images to the server via the edit image API, but nothing is happening on the server.
## Diagnosis Performed
### 1. **API Endpoint Status** ✅
- **Endpoint**: `POST /api/player-edit-media`
- **Status**: Exists and properly implemented
- **Location**: `app/blueprints/api.py` (lines 711-851)
- **Authentication**: Requires Bearer token with valid player auth code
### 2. **Bug Found and Fixed** 🐛
Found undefined variable bug in the `receive_edited_media()` function:
- **Issue**: `playlist` variable was only defined inside an `if` block
- **Problem**: When a player has no assigned playlist, the variable remained undefined
- **Error**: Would cause `UnboundLocalError` when trying to return the response
- **Fix**: Initialize `playlist = None` before the conditional block
- **Commit**: `8a89df3`
### 3. **Server Logs Check** ✅
- No `player-edit-media` requests found in recent logs
- **Conclusion**: Requests are not reaching the server, indicating a client-side issue
## Possible Root Causes
### A. **Player App Not Sending Requests**
The player application might not be calling the edit media endpoint. Check:
- Is the "edit on player" feature enabled for the content?
- Does the player app have code to capture edited images?
- Are there errors in the player app logs?
### B. **Wrong Endpoint URL**
If the player app is hardcoded with an incorrect URL, requests won't reach the server.
- **Expected URL**: `{server_url}/api/player-edit-media`
- **Required Header**: `Authorization: Bearer {player_auth_code}`
### C. **Network Issues**
- Firewall blocking requests
- Network connectivity issues between player and server
- SSL/HTTPS certificate validation failures
### D. **Request Format Issues**
The endpoint expects:
```
Content-Type: multipart/form-data
- image_file: The edited image file (binary)
- metadata: JSON string with this structure:
{
"time_of_modification": "2026-01-17T19:50:00Z",
"original_name": "image.jpg",
"new_name": "image_v1.jpg",
"version": 1,
"user_card_data": "optional_user_code"
}
```
### E. **Authentication Issues**
- Player's auth code might be invalid
- Bearer token not being sent correctly
- Auth code might have changed
## Testing Steps
### 1. **Verify Endpoint is Accessible**
```bash
curl -X POST http://localhost:5000/api/player-edit-media \
-H "Authorization: Bearer <valid_player_auth_code>" \
-F "image_file=@test.jpg" \
-F "metadata={\"time_of_modification\":\"2026-01-17T20:00:00Z\",\"original_name\":\"4k1.jpg\",\"new_name\":\"4k1_v1.jpg\",\"version\":1}"
```
### 2. **Check Player Logs**
Look for errors in the player application logs when attempting to send edits
### 3. **Monitor Server Logs**
Enable debug logging and watch for:
```bash
docker compose logs digiserver-app -f | grep -i "edit\|player-edit"
```
### 4. **Verify Player Has Valid Auth Code**
```bash
curl -X POST http://localhost:5000/api/auth/verify \
-H "Authorization: Bearer <player_auth_code>" \
-H "Content-Type: application/json"
```
## Server API Response
### Success Response (200 OK)
```json
{
"success": true,
"message": "Edited media received and processed",
"edit_id": 123,
"version": 1,
"old_filename": "image.jpg",
"new_filename": "image_v1.jpg",
"new_playlist_version": 34
}
```
### Error Responses
- **401**: Missing or invalid authorization header
- **403**: Invalid authentication code
- **400**: Missing required fields (image_file, metadata, etc.)
- **404**: Original content file not found in system
- **500**: Internal server error (check logs)
## Expected Server Behavior
When an edit is successfully received:
1. ✅ File is saved to `/static/uploads/edited_media/<content_id>/<filename>`
2. ✅ Metadata JSON is saved alongside the file
3. ✅ PlayerEdit record is created in database
4. ✅ PlayerUser record is auto-created if user_card_data provided
5. ✅ Playlist version is incremented (if player has assigned playlist)
6. ✅ Playlist cache is cleared
7. ✅ Action is logged in server_log table
## Database Records
After successful upload, check:
```sql
-- Player edit records
SELECT * FROM player_edit WHERE player_id = ? ORDER BY created_at DESC;
-- Verify file exists
ls -la app/static/uploads/edited_media/
-- Check server logs
SELECT * FROM server_log WHERE action LIKE '%edited%' ORDER BY created_at DESC;
```
## Next Steps
1. Check if player app is configured with correct server URL
2. Verify player has "edit on player" enabled for the content
3. Check player app logs for any error messages
4. Test endpoint connectivity using curl/Postman
5. Monitor server logs while player attempts to send an edit
6. Verify player's auth code is valid and unchanged

View File

@@ -0,0 +1,420 @@
#!/usr/bin/env python3
"""
Diagnostic script to test the player edit media API endpoint.
This script simulates what a player would do when uploading edited images.
"""
import requests
import json
import sys
from datetime import datetime
from pathlib import Path
# Color codes for output
class Colors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def print_section(title):
"""Print a section header"""
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}")
print(f"{Colors.HEADER}{Colors.BOLD}{title}{Colors.ENDC}")
print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n")
def print_success(msg):
print(f"{Colors.OKGREEN}{msg}{Colors.ENDC}")
def print_error(msg):
print(f"{Colors.FAIL}{msg}{Colors.ENDC}")
def print_info(msg):
print(f"{Colors.OKCYAN} {msg}{Colors.ENDC}")
def print_warning(msg):
print(f"{Colors.WARNING}{msg}{Colors.ENDC}")
def test_server_health(base_url):
"""Test if server is accessible"""
print_section("1. Testing Server Health")
try:
response = requests.get(f"{base_url}/api/health", timeout=5)
if response.status_code == 200:
print_success(f"Server is accessible at {base_url}")
data = response.json()
print(f" Status: {data.get('status')}")
print(f" Version: {data.get('version')}")
return True
else:
print_error(f"Server returned status {response.status_code}")
return False
except requests.exceptions.ConnectionError:
print_error(f"Cannot connect to server at {base_url}")
return False
except Exception as e:
print_error(f"Error testing server health: {str(e)}")
return False
def test_endpoint_exists(base_url):
"""Test if the endpoint is available"""
print_section("2. Testing Endpoint Availability")
endpoint = f"{base_url}/api/player-edit-media"
print_info(f"Testing endpoint: {endpoint}")
# Test without auth (should get 401)
try:
response = requests.post(endpoint, timeout=5)
if response.status_code == 401:
print_success("Endpoint exists and requires authentication (401)")
print(f" Response: {response.json()}")
return True
elif response.status_code == 404:
print_error("Endpoint NOT FOUND (404) - The endpoint doesn't exist!")
return False
elif response.status_code == 400:
print_warning("Endpoint exists but got 400 (Bad Request) - likely missing data")
print(f" Response: {response.json()}")
return True
else:
print_warning(f"Unexpected status code: {response.status_code}")
print(f" Response: {response.text}")
return True
except requests.exceptions.ConnectionError:
print_error("Cannot connect to endpoint")
return False
except Exception as e:
print_error(f"Error testing endpoint: {str(e)}")
return False
def get_player_auth_code(base_url, db_path):
"""Get a valid player auth code from the database"""
print_section("3. Retrieving Player Auth Code")
try:
import sqlite3
# Try to connect to the database
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT id, name, auth_code FROM player LIMIT 1")
result = cursor.fetchone()
if result:
player_id, player_name, auth_code = result
print_success(f"Found player: {player_name} (ID: {player_id})")
print_info(f"Auth code: {auth_code[:10]}...{auth_code[-5:]}")
# Get playlist for this player
cursor.execute("SELECT playlist_id FROM player WHERE id = ?", (player_id,))
playlist_row = cursor.fetchone()
has_playlist = playlist_row and playlist_row[0] is not None
print_info(f"Has assigned playlist: {has_playlist}")
conn.close()
return player_id, player_name, auth_code
else:
print_error("No players found in database")
conn.close()
return None, None, None
except sqlite3.OperationalError as e:
print_error(f"Cannot access database at {db_path}")
print_warning("Make sure you're running this from the correct directory")
return None, None, None
except Exception as e:
print_error(f"Error retrieving player auth code: {str(e)}")
return None, None, None
def get_sample_content(base_url, auth_code):
"""Get a sample content file to use for testing"""
print_section("4. Retrieving Sample Content")
try:
headers = {"Authorization": f"Bearer {auth_code}"}
# Get player ID from auth
player_response = requests.get(
f"{base_url}/api/health",
headers=headers,
timeout=5
)
# Try to get a playlist
# We need to query the database for this
print_warning("Getting sample content from filesystem...")
uploads_dir = Path("app/static/uploads")
if uploads_dir.exists():
# Find a non-edited media file
image_files = list(uploads_dir.glob("*.jpg")) + list(uploads_dir.glob("*.png"))
if image_files:
sample_file = image_files[0]
print_success(f"Found sample image: {sample_file.name}")
return sample_file.name, sample_file
print_warning("No sample images found in uploads directory")
return None, None
except Exception as e:
print_error(f"Error getting sample content: {str(e)}")
return None, None
def test_authentication(base_url, auth_code):
"""Test if authentication works"""
print_section("5. Testing Authentication")
try:
headers = {"Authorization": f"Bearer {auth_code}"}
response = requests.post(
f"{base_url}/api/player-edit-media",
headers=headers,
timeout=5
)
if response.status_code == 401:
print_error("Authentication FAILED - Invalid auth code")
print(f" Response: {response.json()}")
return False
elif response.status_code == 400:
print_success("Authentication passed! (Got 400 because of missing data)")
print(f" Response: {response.json()}")
return True
elif response.status_code == 404:
print_error("Endpoint not found!")
return False
else:
print_warning(f"Unexpected status: {response.status_code}")
print(f" Response: {response.text}")
return True
except Exception as e:
print_error(f"Error testing authentication: {str(e)}")
return False
def test_full_upload(base_url, auth_code, content_filename, sample_file):
"""Test a full media upload"""
print_section("6. Testing Full Media Upload")
if not auth_code or not content_filename or not sample_file:
print_error("Missing required parameters for upload test")
return False
try:
# Create metadata
metadata = {
"time_of_modification": datetime.utcnow().isoformat() + "Z",
"original_name": content_filename,
"new_name": f"{content_filename.split('.')[0]}_v1.{content_filename.split('.')[-1]}",
"version": 1,
"user_card_data": "test_user_123"
}
print_info(f"Preparing upload with metadata:")
print(f" Original: {metadata['original_name']}")
print(f" New name: {metadata['new_name']}")
print(f" Version: {metadata['version']}")
# Prepare the request
headers = {"Authorization": f"Bearer {auth_code}"}
with open(sample_file, 'rb') as f:
files = {
'image_file': (sample_file.name, f, 'image/jpeg'),
'metadata': (None, json.dumps(metadata))
}
print_info("Sending upload request...")
response = requests.post(
f"{base_url}/api/player-edit-media",
headers=headers,
files=files,
timeout=10
)
print(f" Status Code: {response.status_code}")
if response.status_code == 200:
data = response.json()
print_success("Upload successful!")
print(f" Response: {json.dumps(data, indent=2)}")
return True
elif response.status_code == 404:
print_error("Content not found - The original_name doesn't match any content in database")
print(f" Error: {response.json()}")
return False
elif response.status_code == 400:
print_error("Bad Request - Check metadata format")
print(f" Error: {response.json()}")
return False
elif response.status_code == 401:
print_error("Authentication failed")
print(f" Error: {response.json()}")
return False
else:
print_error(f"Upload failed with status {response.status_code}")
print(f" Response: {response.text}")
return False
except Exception as e:
print_error(f"Error during upload: {str(e)}")
import traceback
traceback.print_exc()
return False
def check_database_integrity(db_path):
"""Check database tables and records"""
print_section("7. Database Integrity Check")
try:
import sqlite3
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check player table
cursor.execute("SELECT COUNT(*) FROM player")
player_count = cursor.fetchone()[0]
print_info(f"Players in database: {player_count}")
# Check content table
cursor.execute("SELECT COUNT(*) FROM content")
content_count = cursor.fetchone()[0]
print_info(f"Content items in database: {content_count}")
# Check player_edit table
cursor.execute("SELECT COUNT(*) FROM player_edit")
edit_count = cursor.fetchone()[0]
print_info(f"Player edits recorded: {edit_count}")
# List content files
print_info("Sample content files:")
cursor.execute("SELECT id, filename, content_type FROM content LIMIT 5")
for row in cursor.fetchall():
print(f" - [{row[0]}] {row[1]} ({row[2]})")
conn.close()
print_success("Database integrity check passed")
return True
except Exception as e:
print_error(f"Database integrity check failed: {str(e)}")
return False
def main():
"""Run all diagnostic tests"""
print(f"{Colors.BOLD}{Colors.OKCYAN}")
print("""
╔═══════════════════════════════════════════════════════════╗
║ DIGISERVER EDIT MEDIA API - DIAGNOSTIC SCRIPT ║
║ Testing Player Edit Upload Functionality ║
╚═══════════════════════════════════════════════════════════╝
""")
print(Colors.ENDC)
# Configuration
base_url = "http://localhost:5000" # Change this if server is on different host
db_path = "instance/digiserver.db"
print_info(f"Server URL: {base_url}")
print_info(f"Database: {db_path}\n")
# Run tests
tests_passed = []
tests_failed = []
# Test 1: Server health
if test_server_health(base_url):
tests_passed.append("Server Health")
else:
tests_failed.append("Server Health")
print_error("Cannot continue without server access")
return
# Test 2: Endpoint exists
if test_endpoint_exists(base_url):
tests_passed.append("Endpoint Availability")
else:
tests_failed.append("Endpoint Availability")
print_error("Cannot continue - endpoint doesn't exist!")
return
# Test 3: Get player auth code
player_id, player_name, auth_code = get_player_auth_code(base_url, db_path)
if auth_code:
tests_passed.append("Player Auth Code Retrieval")
else:
tests_failed.append("Player Auth Code Retrieval")
print_error("Cannot continue without valid player auth code")
return
# Test 4: Authentication
if test_authentication(base_url, auth_code):
tests_passed.append("Authentication")
else:
tests_failed.append("Authentication")
print_error("Authentication test failed")
# Test 5: Get sample content
content_name, sample_file = get_sample_content(base_url, auth_code)
if content_name and sample_file:
tests_passed.append("Sample Content Retrieval")
# Test 6: Full upload
if test_full_upload(base_url, auth_code, content_name, sample_file):
tests_passed.append("Full Media Upload")
else:
tests_failed.append("Full Media Upload")
else:
tests_failed.append("Sample Content Retrieval")
# Test 7: Database integrity
if check_database_integrity(db_path):
tests_passed.append("Database Integrity")
else:
tests_failed.append("Database Integrity")
# Summary
print_section("Summary")
if tests_passed:
print(f"{Colors.OKGREEN}Passed Tests ({len(tests_passed)}):{Colors.ENDC}")
for test in tests_passed:
print(f" {Colors.OKGREEN}{Colors.ENDC} {test}")
if tests_failed:
print(f"\n{Colors.FAIL}Failed Tests ({len(tests_failed)}):{Colors.ENDC}")
for test in tests_failed:
print(f" {Colors.FAIL}{Colors.ENDC} {test}")
print(f"\n{Colors.BOLD}Result: {len(tests_passed)}/{len(tests_passed) + len(tests_failed)} tests passed{Colors.ENDC}\n")
# Recommendations
print_section("Recommendations")
if "Endpoint Availability" in tests_failed:
print_warning("The /api/player-edit-media endpoint is not available")
print(" 1. Check if the Flask app reloaded after code changes")
print(" 2. Verify the endpoint is properly registered in api.py")
print(" 3. Restart the Docker container")
if "Full Media Upload" in tests_failed:
print_warning("Upload test failed - check:")
print(" 1. The original_name matches actual content filenames")
print(" 2. Content record exists in the database")
print(" 3. Server has permission to write to uploads directory")
print(" 4. Check server logs for error details")
if "Authentication" in tests_failed:
print_warning("Authentication failed - check:")
print(" 1. Player auth code is valid and hasn't expired")
print(" 2. Auth header format is correct: 'Authorization: Bearer <code>'")
print(" 3. Player record hasn't been deleted from database")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""
Simplified diagnostic script using Flask's built-in test client.
This script tests the player edit media API endpoint.
"""
import sys
import json
from datetime import datetime
from pathlib import Path
# Add the app directory to the path
sys.path.insert(0, '/app')
from app import create_app
from app.extensions import db
from app.models import Player, Content
def test_edit_media_endpoint():
"""Test the edit media endpoint using Flask test client"""
print("\n" + "="*60)
print("DIGISERVER EDIT MEDIA API - DIAGNOSTIC TEST")
print("="*60 + "\n")
# Create app context
app = create_app()
with app.app_context():
# Get a test client
client = app.test_client()
# Test 1: Check server health
print("[1/6] Testing server health...")
response = client.get('/api/health')
if response.status_code == 200:
print(f" ✓ Server is healthy (Status: {response.status_code})")
data = response.json
print(f" Version: {data.get('version')}")
else:
print(f" ✗ Server health check failed (Status: {response.status_code})")
return
# Test 2: Check endpoint without auth
print("\n[2/6] Testing endpoint availability (without auth)...")
response = client.post('/api/player-edit-media')
if response.status_code == 401:
print(f" ✓ Endpoint exists and requires auth (Status: 401)")
print(f" Response: {response.json}")
elif response.status_code == 404:
print(f" ✗ ENDPOINT NOT FOUND! (Status: 404)")
print(f" The /api/player-edit-media route is not registered!")
return
else:
print(f" ⚠ Unexpected status: {response.status_code}")
print(f" Response: {response.json}")
# Test 3: Get player and auth code
print("\n[3/6] Retrieving player credentials...")
player = Player.query.first()
if not player:
print(" ✗ No players found in database!")
return
print(f" ✓ Found player: {player.name}")
print(f" Player ID: {player.id}")
print(f" Auth Code: {player.auth_code[:10]}...{player.auth_code[-5:]}")
print(f" Has assigned playlist: {player.playlist_id is not None}")
# Test 4: Test authentication
print("\n[4/6] Testing authentication...")
headers = {
'Authorization': f'Bearer {player.auth_code}'
}
response = client.post('/api/player-edit-media', headers=headers)
if response.status_code == 401:
print(f" ✗ Authentication FAILED!")
print(f" Response: {response.json}")
return
elif response.status_code == 400:
print(f" ✓ Authentication successful!")
print(f" Got 400 (missing data) which means auth passed")
else:
print(f" ⚠ Unexpected response: {response.status_code}")
# Test 5: Get sample content
print("\n[5/6] Finding sample content...")
content = Content.query.first()
if not content:
print(" ✗ No content found in database!")
return
print(f" ✓ Found content: {content.filename}")
print(f" Content ID: {content.id}")
print(f" Type: {content.content_type}")
print(f" Duration: {content.duration}s")
# Check if file exists on disk
file_path = Path(f'/app/app/static/uploads/{content.filename}')
file_exists = file_path.exists()
print(f" File exists on disk: {file_exists}")
# Test 6: Simulate upload
print("\n[6/6] Simulating media upload...")
metadata = {
"time_of_modification": datetime.utcnow().isoformat() + "Z",
"original_name": content.filename,
"new_name": f"{content.filename.split('.')[0]}_v1.{content.filename.split('.')[-1]}",
"version": 1,
"user_card_data": "test_user_123"
}
print(f" Metadata prepared:")
print(f" Original: {metadata['original_name']}")
print(f" New name: {metadata['new_name']}")
print(f" Version: {metadata['version']}")
# Create a dummy file
dummy_file_data = b"fake image data for testing"
# Send request with multipart data
data = {
'metadata': json.dumps(metadata)
}
# Use Flask test client's multipart support
response = client.post(
'/api/player-edit-media',
headers=headers,
data=data,
content_type='multipart/form-data'
)
print(f"\n Response Status: {response.status_code}")
if response.status_code == 200:
print(f" ✓ UPLOAD SUCCESSFUL!")
resp_data = response.json
print(f" Response: {json.dumps(resp_data, indent=6)}")
elif response.status_code == 400:
resp = response.json
error_msg = resp.get('error', 'Unknown error')
print(f" ⚠ Bad Request (400): {error_msg}")
print(f" Full response: {resp}")
elif response.status_code == 404:
resp = response.json
error_msg = resp.get('error', 'Unknown error')
print(f" ✗ Not Found (404): {error_msg}")
print(f" Make sure content filename matches exactly")
else:
print(f" ✗ Upload failed with status {response.status_code}")
print(f" Response: {response.data.decode('utf-8')}")
# Summary
print("\n" + "="*60)
print("DIAGNOSTICS SUMMARY")
print("="*60)
print(f"""
Endpoint Status:
- Route exists: YES
- Authentication: Working
- Test content available: YES
- Database accessible: YES
Recommendations:
1. The endpoint IS working and accessible
2. Check player application logs for upload errors
3. Verify player is sending correct request format
4. Make sure player has valid authorization code
5. Check network connectivity between player and server
""")
if __name__ == "__main__":
try:
test_edit_media_endpoint()
except Exception as e:
print(f"\n✗ ERROR: {str(e)}")
import traceback
traceback.print_exc()