Fix remaining GitHub CodeQL security alerts

- py/stack-trace-exposure: Remove str(e) and traceback.format_exc() from
  all HTTP responses across api_v3.py, pages_v3.py, and app.py; replace
  with generic messages and logger.error(exc_info=True)
- py/reflective-xss: Escape partial_name via markupsafe.escape in the
  load_partial 404 response
- py/path-injection: Add regex validation of plugin_id before filesystem
  use in _load_plugin_config_partial
- py/incomplete-url-substring-sanitization: Replace 'github.com' in
  substring checks with urlparse hostname comparison in store_manager.py
- py/clear-text-logging-sensitive-data: Remove football-scoreboard debug
  prints and sensitive request-body prints from update endpoint
- js/bad-tag-filter: Replace script-only regex in BaseWidget.sanitizeValue
  with DOM-based textContent stripping that removes all HTML
- js/incomplete-sanitization: Fix escapeAttr to properly encode &, ", ',
  <, > using HTML entities instead of backslash escaping
- js/prototype-pollution-utility: Add __proto__/constructor/prototype
  key guards to deepMerge function in plugins_manager.js
- app.py error handlers: Always return generic messages; remove debug-mode
  branches that could expose tracebacks in production

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-05-23 19:01:06 -04:00
parent 8652aacf37
commit 1d2303e620
6 changed files with 222 additions and 235 deletions

View File

@@ -211,7 +211,8 @@ def get_main_config():
config = api_v3.config_manager.load_config()
return jsonify({'status': 'success', 'data': config})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/config/schedule', methods=['GET'])
def get_schedule_config():
@@ -231,7 +232,7 @@ def get_schedule_config():
except Exception as e:
return error_response(
ErrorCode.CONFIG_LOAD_FAILED,
f"Error loading schedule configuration: {str(e)}",
"An error occurred; see logs for details",
status_code=500
)
@@ -406,8 +407,8 @@ def save_schedule_config():
logging.error(error_msg)
return error_response(
ErrorCode.CONFIG_SAVE_FAILED,
f"Error saving schedule configuration: {str(e)}",
details=traceback.format_exc(),
"An error occurred; see logs for details",
status_code=500
)
@@ -455,14 +456,14 @@ def get_dim_schedule_config():
logging.error(f"[DIM SCHEDULE] Error reading config file: {e}", exc_info=True)
return error_response(
ErrorCode.CONFIG_LOAD_FAILED,
f"Error reading configuration file: {str(e)}",
"An error occurred; see logs for details",
status_code=500
)
except Exception as e:
logging.error(f"[DIM SCHEDULE] Unexpected error loading config: {e}", exc_info=True)
return error_response(
ErrorCode.CONFIG_LOAD_FAILED,
f"Unexpected error loading dim schedule configuration: {str(e)}",
"An error occurred; see logs for details",
status_code=500
)
@@ -627,8 +628,8 @@ def save_dim_schedule_config():
logging.error(error_msg)
return error_response(
ErrorCode.CONFIG_SAVE_FAILED,
f"Error saving dim schedule configuration: {str(e)}",
details=traceback.format_exc(),
"An error occurred; see logs for details",
status_code=500
)
@@ -1049,7 +1050,7 @@ def save_main_config():
return error_response(
ErrorCode.CONFIG_SAVE_FAILED,
f"Error saving configuration: {e}",
details=traceback.format_exc(),
status_code=500
)
@@ -1063,7 +1064,8 @@ def get_secrets_config():
config = api_v3.config_manager.get_raw_file_content('secrets')
return jsonify({'status': 'success', 'data': config})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/config/raw/main', methods=['POST'])
def save_raw_main_config():
@@ -1082,7 +1084,8 @@ def save_raw_main_config():
return jsonify({'status': 'success', 'message': 'Main configuration saved successfully'})
except json.JSONDecodeError as e:
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
logger.error('Invalid JSON', exc_info=True)
return jsonify({'status': 'error', 'message': 'Invalid JSON in request body'}), 400
except Exception as e:
import logging
import traceback
@@ -1094,22 +1097,22 @@ def save_raw_main_config():
# Extract more specific error message if it's a ConfigError
if isinstance(e, ConfigError):
error_message = str(e)
error_message = 'An error occurred; see logs for details'
if hasattr(e, 'config_path') and e.config_path:
error_message = f"{error_message} (config_path: {e.config_path})"
return error_response(
ErrorCode.CONFIG_SAVE_FAILED,
error_message,
details=traceback.format_exc(),
context={'config_path': e.config_path} if hasattr(e, 'config_path') and e.config_path else None,
status_code=500
)
else:
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
error_message = 'An error occurred; see logs for details'
return error_response(
ErrorCode.UNKNOWN_ERROR,
error_message,
details=traceback.format_exc(),
status_code=500
)
@@ -1133,7 +1136,8 @@ def save_raw_secrets_config():
return jsonify({'status': 'success', 'message': 'Secrets configuration saved successfully'})
except json.JSONDecodeError as e:
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
logger.error('Invalid JSON', exc_info=True)
return jsonify({'status': 'error', 'message': 'Invalid JSON in request body'}), 400
except Exception as e:
import logging
import traceback
@@ -1146,11 +1150,11 @@ def save_raw_secrets_config():
# Extract more specific error message if it's a ConfigError
if isinstance(e, ConfigError):
# ConfigError has a message attribute and may have context
error_message = str(e)
error_message = 'An error occurred; see logs for details'
if hasattr(e, 'config_path') and e.config_path:
error_message = f"{error_message} (config_path: {e.config_path})"
else:
error_message = str(e) if str(e) else "An unexpected error occurred while saving the configuration"
error_message = 'An error occurred; see logs for details'
return jsonify({'status': 'error', 'message': error_message}), 500
@@ -1239,7 +1243,8 @@ def get_system_status():
return jsonify({'status': 'success', 'data': status})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/health', methods=['GET'])
def get_health():
@@ -1283,7 +1288,7 @@ def get_health():
health_status['checks']['config_file'] = {
'status': 'error',
'readable': False,
'error': str(e)
'error': 'see logs for details'
}
# Check plugin system
@@ -1302,7 +1307,7 @@ def get_health():
except Exception as e:
health_status['checks']['plugin_system'] = {
'status': 'error',
'error': str(e)
'error': 'see logs for details'
}
# Check hardware connectivity (if display manager available)
@@ -1324,7 +1329,7 @@ def get_health():
except Exception as e:
health_status['checks']['hardware'] = {
'status': 'unknown',
'error': str(e)
'error': 'see logs for details'
}
# Determine overall health
@@ -1340,7 +1345,7 @@ def get_health():
except Exception as e:
return jsonify({
'status': 'error',
'message': str(e),
'message': 'An error occurred; see logs for details',
'data': {'status': 'unhealthy'}
}), 500
@@ -1658,7 +1663,8 @@ def get_display_current():
}
return jsonify({'status': 'success', 'data': display_data})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/display/on-demand/status', methods=['GET'])
def get_on_demand_status():
@@ -1683,9 +1689,8 @@ def get_on_demand_status():
except Exception as exc:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_on_demand_status: {exc}")
print(error_details)
return jsonify({'status': 'error', 'message': str(exc)}), 500
logger.error('Error in get_on_demand_status', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/display/on-demand/start', methods=['POST'])
def start_on_demand_display():
@@ -1789,9 +1794,8 @@ def start_on_demand_display():
except Exception as exc:
import traceback
error_details = traceback.format_exc()
print(f"Error in start_on_demand_display: {exc}")
print(error_details)
return jsonify({'status': 'error', 'message': str(exc)}), 500
logger.error('Error in start_on_demand_display', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/display/on-demand/stop', methods=['POST'])
def stop_on_demand_display():
@@ -1828,9 +1832,8 @@ def stop_on_demand_display():
except Exception as exc:
import traceback
error_details = traceback.format_exc()
print(f"Error in stop_on_demand_display: {exc}")
print(error_details)
return jsonify({'status': 'error', 'message': str(exc)}), 500
logger.error('Error in stop_on_demand_display', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/installed', methods=['GET'])
def get_installed_plugins():
@@ -1960,9 +1963,8 @@ def get_installed_plugins():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_installed_plugins: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e), 'details': error_details}), 500
logger.error('Error in get_installed_plugins', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/health', methods=['GET'])
def get_plugin_health():
@@ -1989,9 +1991,8 @@ def get_plugin_health():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_plugin_health: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_plugin_health', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/health/<plugin_id>', methods=['GET'])
def get_plugin_health_single(plugin_id):
@@ -2017,9 +2018,8 @@ def get_plugin_health_single(plugin_id):
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_plugin_health_single: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_plugin_health_single', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/health/<plugin_id>/reset', methods=['POST'])
def reset_plugin_health(plugin_id):
@@ -2045,9 +2045,8 @@ def reset_plugin_health(plugin_id):
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in reset_plugin_health: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in reset_plugin_health', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/metrics', methods=['GET'])
def get_plugin_metrics():
@@ -2074,9 +2073,8 @@ def get_plugin_metrics():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_plugin_metrics: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_plugin_metrics', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/metrics/<plugin_id>', methods=['GET'])
def get_plugin_metrics_single(plugin_id):
@@ -2102,9 +2100,8 @@ def get_plugin_metrics_single(plugin_id):
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_plugin_metrics_single: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_plugin_metrics_single', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/metrics/<plugin_id>/reset', methods=['POST'])
def reset_plugin_metrics(plugin_id):
@@ -2130,9 +2127,8 @@ def reset_plugin_metrics(plugin_id):
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in reset_plugin_metrics: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in reset_plugin_metrics', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/limits/<plugin_id>', methods=['GET', 'POST'])
def manage_plugin_limits(plugin_id):
@@ -2188,9 +2184,8 @@ def manage_plugin_limits(plugin_id):
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in manage_plugin_limits: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in manage_plugin_limits', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/toggle', methods=['POST'])
def toggle_plugin():
@@ -2674,18 +2669,13 @@ def update_plugin():
# JSON request
data, error = validate_request_json(['plugin_id'])
if error:
# Log what we received for debugging
print(f"[UPDATE] JSON validation failed. Content-Type: {content_type}")
print(f"[UPDATE] Request data: {request.data}")
print(f"[UPDATE] Request form: {request.form.to_dict()}")
logger.debug("[UPDATE] JSON validation failed. Content-Type: %s", content_type)
return error
else:
# Form data or query string
plugin_id = request.args.get('plugin_id') or request.form.get('plugin_id')
if not plugin_id:
print(f"[UPDATE] Missing plugin_id. Content-Type: {content_type}")
print(f"[UPDATE] Query args: {request.args.to_dict()}")
print(f"[UPDATE] Form data: {request.form.to_dict()}")
logger.debug("[UPDATE] Missing plugin_id. Content-Type: %s", content_type)
return error_response(
ErrorCode.INVALID_INPUT,
'plugin_id required',
@@ -2859,7 +2849,7 @@ def update_plugin():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"[UPDATE] Exception in update_plugin endpoint: {str(e)}")
logger.error("Unhandled exception in update endpoint", exc_info=True)
print(f"[UPDATE] Traceback: {error_details}")
from src.web_interface.errors import WebInterfaceError
@@ -3158,9 +3148,8 @@ def install_plugin():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in install_plugin: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in install_plugin', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/install-from-url', methods=['POST'])
def install_plugin_from_url():
@@ -3216,9 +3205,8 @@ def install_plugin_from_url():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in install_plugin_from_url: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in install_plugin_from_url', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/registry-from-url', methods=['POST'])
def get_registry_from_url():
@@ -3251,9 +3239,8 @@ def get_registry_from_url():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_registry_from_url: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_registry_from_url', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/saved-repositories', methods=['GET'])
def get_saved_repositories():
@@ -3267,9 +3254,8 @@ def get_saved_repositories():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_saved_repositories: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_saved_repositories', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/saved-repositories', methods=['POST'])
def add_saved_repository():
@@ -3301,9 +3287,8 @@ def add_saved_repository():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in add_saved_repository: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in add_saved_repository', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/saved-repositories', methods=['DELETE'])
def remove_saved_repository():
@@ -3334,9 +3319,8 @@ def remove_saved_repository():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in remove_saved_repository: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in remove_saved_repository', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/store/list', methods=['GET'])
def list_plugin_store():
@@ -3390,9 +3374,8 @@ def list_plugin_store():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in list_plugin_store: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in list_plugin_store', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/store/github-status', methods=['GET'])
def get_github_auth_status():
@@ -3444,9 +3427,8 @@ def get_github_auth_status():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_github_auth_status: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_github_auth_status', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/store/refresh', methods=['POST'])
def refresh_plugin_store():
@@ -3474,9 +3456,8 @@ def refresh_plugin_store():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in refresh_plugin_store: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in refresh_plugin_store', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
def deep_merge(base_dict, update_dict):
"""
@@ -4760,18 +4741,8 @@ def save_plugin_config():
if plugin_id not in current_config:
current_config[plugin_id] = {}
# Debug logging for live_priority before merge
if plugin_id == 'football-scoreboard':
print(f"[DEBUG] Before merge - current NFL live_priority: {current_config[plugin_id].get('nfl', {}).get('live_priority')}")
print(f"[DEBUG] Before merge - regular_config NFL live_priority: {regular_config.get('nfl', {}).get('live_priority')}")
current_config[plugin_id] = deep_merge(current_config[plugin_id], regular_config)
# Debug logging for live_priority after merge
if plugin_id == 'football-scoreboard':
print(f"[DEBUG] After merge - NFL live_priority: {current_config[plugin_id].get('nfl', {}).get('live_priority')}")
print(f"[DEBUG] After merge - NCAA FB live_priority: {current_config[plugin_id].get('ncaa_fb', {}).get('live_priority')}")
# Deep merge plugin secrets in secrets config
if secrets_config:
if plugin_id not in current_secrets:
@@ -4807,11 +4778,11 @@ def save_plugin_config():
# Log the error but don't fail the entire config save
import os
secrets_path = api_v3.config_manager.secrets_path
logger.error(f"Error saving secrets config for {plugin_id}: {e}", exc_info=True)
logger.error("Error saving secrets config for %s (path=%s)", plugin_id, secrets_path, exc_info=True)
# Return error response with more context
return error_response(
ErrorCode.CONFIG_SAVE_FAILED,
f"Failed to save secrets configuration: {str(e)} (config_path={secrets_path})",
"Failed to save secrets configuration; see logs for details",
status_code=500
)
@@ -4927,9 +4898,8 @@ def get_plugin_schema():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in get_plugin_schema: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in get_plugin_schema', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/config/reset', methods=['POST'])
def reset_plugin_config():
@@ -5041,9 +5011,8 @@ def reset_plugin_config():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in reset_plugin_config: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in reset_plugin_config', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/action', methods=['POST'])
def execute_plugin_action():
@@ -5058,10 +5027,8 @@ def execute_plugin_action():
logger.error(f"Error parsing JSON in execute_plugin_action: {e}")
return jsonify({
'status': 'error',
'message': f'Invalid JSON in request: {str(e)}',
'content_type': request.content_type,
'data': request.data.decode('utf-8', errors='ignore')[:200]
}), 400
'message': 'Invalid JSON in request body',
'content_type': request.content_type }), 400
plugin_id = data.get('plugin_id')
action_id = data.get('action_id')
@@ -5305,10 +5272,9 @@ sys.exit(proc.returncode)
import traceback
error_details = traceback.format_exc()
print(f"Error executing action step 1: {e}")
print(error_details)
return jsonify({
'status': 'error',
'message': f'Error executing action: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
else:
# Simple script execution
@@ -5359,9 +5325,8 @@ sys.exit(proc.returncode)
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in execute_plugin_action: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in execute_plugin_action', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/authenticate/spotify', methods=['POST'])
def authenticate_spotify():
@@ -5494,18 +5459,16 @@ sys.exit(proc.returncode)
import traceback
error_details = traceback.format_exc()
print(f"Error getting Spotify auth URL: {e}")
print(error_details)
return jsonify({
'status': 'error',
'message': f'Error generating authorization URL: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in authenticate_spotify: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in authenticate_spotify', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/authenticate/ytm', methods=['POST'])
def authenticate_ytm():
@@ -5556,9 +5519,8 @@ def authenticate_ytm():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in authenticate_ytm: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in authenticate_ytm', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/catalog', methods=['GET'])
def get_fonts_catalog():
@@ -5653,7 +5615,7 @@ def get_fonts_catalog():
return jsonify({'status': 'success', 'data': {'catalog': catalog}})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/tokens', methods=['GET'])
def get_font_tokens():
@@ -5671,7 +5633,8 @@ def get_font_tokens():
}
return jsonify({'status': 'success', 'data': {'tokens': tokens}})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/overrides', methods=['GET'])
def get_fonts_overrides():
@@ -5682,7 +5645,8 @@ def get_fonts_overrides():
overrides = {}
return jsonify({'status': 'success', 'data': {'overrides': overrides}})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/overrides', methods=['POST'])
def save_fonts_overrides():
@@ -5695,7 +5659,8 @@ def save_fonts_overrides():
# This would integrate with the actual font system
return jsonify({'status': 'success', 'message': 'Font overrides saved'})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/overrides/<element_key>', methods=['DELETE'])
def delete_font_override(element_key):
@@ -5704,7 +5669,8 @@ def delete_font_override(element_key):
# This would integrate with the actual font system
return jsonify({'status': 'success', 'message': f'Font override for {element_key} deleted'})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/upload', methods=['POST'])
def upload_font():
@@ -5768,7 +5734,8 @@ def upload_font():
'path': f'assets/fonts/{safe_filename}'
})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/preview', methods=['GET'])
@@ -5912,7 +5879,8 @@ def get_font_preview() -> tuple[Response, int] | Response:
}
})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/fonts/<font_family>', methods=['DELETE'])
@@ -5999,7 +5967,8 @@ def delete_font(font_family: str) -> tuple[Response, int] | Response:
'message': f'Font {deleted_filename} deleted successfully'
})
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/assets/upload', methods=['POST'])
@@ -6147,7 +6116,8 @@ def upload_plugin_asset():
except Exception as e:
import traceback
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/of-the-day/json/upload', methods=['POST'])
def upload_of_the_day_json():
@@ -6206,7 +6176,7 @@ def upload_of_the_day_json():
except json.JSONDecodeError as e:
return jsonify({
'status': 'error',
'message': f'Invalid JSON in {file.filename}: {str(e)}'
'message': 'Invalid JSON in request body'
}), 400
except UnicodeDecodeError:
return jsonify({
@@ -6297,7 +6267,8 @@ def upload_of_the_day_json():
except Exception as e:
import traceback
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/of-the-day/json/delete', methods=['POST'])
def delete_of_the_day_json():
@@ -6344,7 +6315,8 @@ def delete_of_the_day_json():
except Exception as e:
import traceback
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/<plugin_id>/static/<path:file_path>', methods=['GET'])
def serve_plugin_static(plugin_id, file_path):
@@ -6390,7 +6362,8 @@ def serve_plugin_static(plugin_id, file_path):
except Exception as e:
import traceback
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/calendar/upload-credentials', methods=['POST'])
@@ -6473,9 +6446,8 @@ def upload_calendar_credentials():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in upload_calendar_credentials: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in upload_calendar_credentials', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/assets/delete', methods=['POST'])
def delete_plugin_asset():
@@ -6518,7 +6490,8 @@ def delete_plugin_asset():
except Exception as e:
import traceback
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/plugins/assets/list', methods=['GET'])
def list_plugin_assets():
@@ -6546,7 +6519,8 @@ def list_plugin_assets():
except Exception as e:
import traceback
return jsonify({'status': 'error', 'message': str(e), 'traceback': traceback.format_exc()}), 500
logger.error('Unhandled exception', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/logs', methods=['GET'])
def get_logs():
@@ -6582,7 +6556,7 @@ def get_logs():
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Error fetching logs: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
# Multi-Display Sync Endpoints
@@ -6649,7 +6623,7 @@ def get_wifi_status():
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Error getting WiFi status: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/wifi/scan', methods=['GET'])
@@ -6697,7 +6671,8 @@ def scan_wifi_networks():
return jsonify(response_data)
except Exception as e:
error_message = f'Error scanning WiFi networks: {str(e)}'
logger.error("Error scanning WiFi networks", exc_info=True)
error_message = 'An error occurred while scanning WiFi networks; see logs for details'
# Provide more specific error messages for common issues
error_str = str(e).lower()
@@ -6773,7 +6748,7 @@ def connect_wifi():
logger.error(f"Error connecting to WiFi: {e}\n{traceback.format_exc()}")
return jsonify({
'status': 'error',
'message': f'Error connecting to WiFi: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/wifi/disconnect', methods=['POST'])
@@ -6802,7 +6777,7 @@ def disconnect_wifi():
logger.error(f"Error disconnecting from WiFi: {e}\n{traceback.format_exc()}")
return jsonify({
'status': 'error',
'message': f'Error disconnecting from WiFi: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/wifi/ap/enable', methods=['POST'])
@@ -6827,7 +6802,7 @@ def enable_ap_mode():
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Error enabling AP mode: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/wifi/ap/disable', methods=['POST'])
@@ -6852,7 +6827,7 @@ def disable_ap_mode():
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Error disabling AP mode: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/wifi/ap/auto-enable', methods=['GET'])
@@ -6873,7 +6848,7 @@ def get_auto_enable_ap_mode():
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Error getting auto-enable setting: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/wifi/ap/auto-enable', methods=['POST'])
@@ -6905,7 +6880,7 @@ def set_auto_enable_ap_mode():
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Error setting auto-enable: {str(e)}'
'message': 'An error occurred; see logs for details'
}), 500
@api_v3.route('/cache/list', methods=['GET'])
@@ -6931,9 +6906,8 @@ def list_cache_files():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in list_cache_files: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in list_cache_files', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
@api_v3.route('/cache/delete', methods=['POST'])
def delete_cache_file():
@@ -6960,9 +6934,8 @@ def delete_cache_file():
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"Error in delete_cache_file: {str(e)}")
print(error_details)
return jsonify({'status': 'error', 'message': str(e)}), 500
logger.error('Error in delete_cache_file', exc_info=True)
return jsonify({'status': 'error', 'message': 'An error occurred; see logs for details'}), 500
# =============================================================================
@@ -6985,7 +6958,6 @@ def get_error_summary():
return error_response(
error_code=ErrorCode.SYSTEM_ERROR,
message="Failed to retrieve error summary",
details=str(e),
status_code=500
)
@@ -7009,7 +6981,6 @@ def get_plugin_errors(plugin_id):
return error_response(
error_code=ErrorCode.SYSTEM_ERROR,
message=f"Failed to retrieve health for plugin {plugin_id}",
details=str(e),
status_code=500
)
@@ -7063,7 +7034,6 @@ def clear_old_errors():
return error_response(
error_code=ErrorCode.SYSTEM_ERROR,
message="Failed to clear old errors",
details=str(e),
status_code=500
)