Files

1 line
70 KiB
JSON

{"repo": "chauhannilay888-bot/graphico", "files": {".env": {"54ca755d8995878ff92f48c5b8495bed7a4a7074": {"t": 1785745080.2941234, "n": 658, "h": "c91a77474f67ef98", "c": "# Google OAuth\nGOOGLE_CLIENT_ID = 578730974907-criqtv67ocspqdo84b568sj13tiqgv1t.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET = 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\nDEEPSEEK_API_KEY = sk-39db9d2b6ea845f5936adbdf80c9820c\nGOOGLE_AI_API_KEY = AQ.Ab8RN6IC77c8kL1N8UHAXE3YFlIuiFMHBBKFy_bT3HIGW1CthA\nGITHUB_MODELS_API_KEY=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\n\n# Other secrets\nFLASK_SECRET_KEY = abcddcbaabcddcba123321123321qwertyuiop__78900987\nSESSION_SECRET_KEY = QwertyuiopaSdfghjklzxcvbnM-21-12-21-12__78900987\nOAUTH_STATE_SECRET = zxcvbnmasdfghjklqwertyuiop-mnbvcxzzxcvbnm-78900987q2w3e4r5t6y7u8i9o0p\n\n"}, "6638855ed05e203f4a7bfa7ba55ca8f7d53f3302": {"t": 1785747666.866522, "n": 658, "h": "c91a77474f67ef98", "c": "# Google OAuth\nGOOGLE_CLIENT_ID = 578730974907-criqtv67ocspqdo84b568sj13tiqgv1t.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET = 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\nDEEPSEEK_API_KEY = sk-39db9d2b6ea845f5936adbdf80c9820c\nGOOGLE_AI_API_KEY = AQ.Ab8RN6IC77c8kL1N8UHAXE3YFlIuiFMHBBKFy_bT3HIGW1CthA\nGITHUB_MODELS_API_KEY=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\n\n# Other secrets\nFLASK_SECRET_KEY = abcddcbaabcddcba123321123321qwertyuiop__78900987\nSESSION_SECRET_KEY = QwertyuiopaSdfghjklzxcvbnM-21-12-21-12__78900987\nOAUTH_STATE_SECRET = zxcvbnmasdfghjklqwertyuiop-mnbvcxzzxcvbnm-78900987q2w3e4r5t6y7u8i9o0p\n\n"}}, "config/settings.py": {"54ca755d8995878ff92f48c5b8495bed7a4a7074": {"t": 1785746912.3543322, "n": 22105, "h": "1623eaf5b7088478", "c": "\"\"\"\nGraphico Pro - Centralized Configuration Settings\nProduction-grade settings management with environment variable support.\n\nSECURITY WARNING:\n This file contains default values for development ONLY.\n In production, ALL secret keys and credentials MUST be set via\n environment variables. Never commit real secrets to version control.\n\nUsage:\n All settings can be overridden by setting environment variables\n before starting the server. Use the .env file for local development.\n\"\"\"\n\nimport os\nimport secrets\nfrom pathlib import Path\nfrom dotenv import load_dotenv\n\n# Load environment variables from .env file (if it exists)\n# .env should NEVER be committed to version control\nenv_path = Path(__file__).resolve().parent.parent / \".env\"\nif env_path.exists():\n load_dotenv(env_path)\nelse:\n # Try to load from current working directory as fallback\n load_dotenv()\n\n# ============================================================================\n# BASE DIRECTORY\n# ============================================================================\n\n# Absolute path to the project root directory\nBASE_DIR = Path(__file__).resolve().parent.parent\n\n# ============================================================================\n# SERVER CONFIGURATION\n# ============================================================================\n\nSERVER_HOST = os.getenv(\"SERVER_HOST\", \"localhost\")\nSERVER_PORT = int(os.getenv(\"SERVER_PORT\", \"8501\"))\n\n# WARNING: Never enable DEBUG in production\nSERVER_DEBUG = os.getenv(\"SERVER_DEBUG\", \"False\").lower() == \"true\"\n\n# Number of worker threads for the development server\nSERVER_WORKERS = int(os.getenv(\"SERVER_WORKERS\", \"4\"))\n\n# Secret key for Flask session signing\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nFLASK_SECRET_KEY = os.getenv(\n \"FLASK_SECRET_KEY\",\n \"graphico-pro-flask-secret-key-DEV-ONLY-change-in-production\"\n)\n\n# ============================================================================\n# CORS CONFIGURATION\n# ============================================================================\n\n# Origins allowed to make cross-origin requests to the API\nALLOWED_ORIGINS = [\n \"http://localhost:5500\",\n \"http://127.0.0.1:5500\",\n \"https://graphico.streamlit.app\",\n \"https://graphico-backend.streamlit.app\"\n]\n\n# HTTP methods allowed for CORS requests\nALLOWED_METHODS = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"]\n\n# HTTP headers allowed for CORS requests\nALLOWED_HEADERS = [\n \"Content-Type\",\n \"Authorization\",\n \"X-Requested-With\",\n \"X-Session-Token\",\n \"X-Request-ID\",\n]\n\n# Allow credentials (cookies, authorization headers) in CORS requests\nALLOWED_CREDENTIALS = True\n\n# ============================================================================\n# GOOGLE OAUTH CONFIGURATION\n# ============================================================================\n\n# Google OAuth 2.0 Client ID\n# This is public and can be in version control\nGOOGLE_CLIENT_ID = os.getenv(\n \"GOOGLE_CLIENT_ID\",\n \"578730974907-criqtv67ocspqdo84b568sj13tiqgv1t.apps.googleusercontent.com\"\n)\n\n# Google OAuth 2.0 Client Secret\n# WARNING: THIS MUST BE SET VIA ENVIRONMENT VARIABLE IN PRODUCTION\n# NEVER commit the actual secret to version control\n# Get it from: https://console.cloud.google.com/apis/credentials\nGOOGLE_CLIENT_SECRET = os.getenv(\"GOOGLE_CLIENT_SECRET\", \"\")\n\n# Allowed redirect URIs (must match Google Cloud Console configuration)\nGOOGLE_REDIRECT_URIS = [\n \"http://localhost:5500\",\n \"https://graphico.streamlit.app\",\n \"https://graphico-backend.streamlit.app\"\n]\n\n# Google OAuth 2.0 endpoints\nGOOGLE_TOKEN_URI = \"https://oauth2.googleapis.com/token\"\nGOOGLE_AUTH_URI = \"https://accounts.google.com/o/oauth2/auth\"\nGOOGLE_USERINFO_URI = \"https://www.googleapis.com/oauth2/v3/userinfo\"\nGOOGLE_CERTS_URI = \"https://www.googleapis.com/oauth2/v3/certs\"\n\n# OAuth scopes requested from Google\nGOOGLE_SCOPES = [\n \"openid\",\n \"https://www.googleapis.com/auth/userinfo.email\",\n \"https://www.googleapis.com/auth/userinfo.profile\",\n]\n\n# ============================================================================\n# CSRF / STATE PROTECTION\n# ============================================================================\n\n# Secret used to sign OAuth state tokens for CSRF protection\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nOAUTH_STATE_SECRET = os.getenv(\n \"OAUTH_STATE_SECRET\",\n \"graphico-oauth-state-secret-DEV-ONLY-change-in-production\"\n)\n\n# Maximum age of OAuth state tokens in seconds (10 minutes)\nOAUTH_STATE_MAX_AGE_SECONDS = int(os.getenv(\"OAUTH_STATE_MAX_AGE_SECONDS\", \"600\"))\n\n# ============================================================================\n# SESSION CONFIGURATION\n# ============================================================================\n\n# Secret key for signing session tokens\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nSESSION_SECRET_KEY = os.getenv(\n \"SESSION_SECRET_KEY\",\n \"graphico-pro-session-secret-DEV-ONLY-change-in-production\"\n)\n\n# Session expiration time in hours (default: 24 hours)\nSESSION_EXPIRY_HOURS = int(os.getenv(\"SESSION_EXPIRY_HOURS\", \"24\"))\n\n# Session cookie name\nSESSION_COOKIE_NAME = os.getenv(\"SESSION_COOKIE_NAME\", \"graphico_session\")\n\n# Cookie security settings\n# WARNING: Set SESSION_COOKIE_SECURE=True in production (HTTPS only)\nSESSION_COOKIE_SECURE = os.getenv(\"SESSION_COOKIE_SECURE\", \"False\").lower() == \"true\"\nSESSION_COOKIE_HTTPONLY = True # Always True - prevent JavaScript access\nSESSION_COOKIE_SAMESITE = os.getenv(\"SESSION_COOKIE_SAMESITE\", \"Lax\")\n\n# Maximum sessions per user (prevents session flooding)\nMAX_SESSIONS_PER_USER = int(os.getenv(\"MAX_SESSIONS_PER_USER\", \"10\"))\n\n# ============================================================================\n# DATABASE CONFIGURATION\n# ============================================================================\n\n# Database backend type: \"json\" | \"sqlite\" | \"postgresql\"\nDATABASE_TYPE = os.getenv(\"DATABASE_TYPE\", \"json\")\n\n# Base directory for all database files\nDATABASE_PATH = BASE_DIR / \"database\" / \"data\"\n\n# JSON Database file paths (current default)\nJSON_USERS_FILE = DATABASE_PATH / \"users.json\"\nJSON_PROJECTS_FILE = DATABASE_PATH / \"projects.json\"\nJSON_HISTORY_FILE = DATABASE_PATH / \"history.json\"\nJSON_SESSIONS_FILE = DATABASE_PATH / \"sessions.json\"\nJSON_OAUTH_STATES_FILE = DATABASE_PATH / \"oauth_states.json\"\n\n# SQLite configuration (future)\nSQLITE_DATABASE_PATH = DATABASE_PATH / \"graphico.db\"\n\n# PostgreSQL configuration (future)\nPOSTGRES_HOST = os.getenv(\"POSTGRES_HOST\", \"localhost\")\nPOSTGRES_PORT = int(os.getenv(\"POSTGRES_PORT\", \"5432\"))\nPOSTGRES_DATABASE = os.getenv(\"POSTGRES_DATABASE\", \"graphico\")\nPOSTGRES_USER = os.getenv(\"POSTGRES_USER\", \"graphico_user\")\n# WARNING: NEVER set a default PostgreSQL password - must be set via env\nPOSTGRES_PASSWORD = os.getenv(\"POSTGRES_PASSWORD\", \"\")\n\n# ============================================================================\n# AI PROVIDER CONFIGURATION\n# ============================================================================\n\n# WARNING: API keys must ALWAYS be set via environment variables\n# Never hardcode API keys or commit them to version control\n\n# GitHub Models (free GPT-4o via Azure)\nGITHUB_MODELS_API_KEY = os.getenv(\"GITHUB_MODELS_API_KEY\", \"\")\n\n# OpenAI API key\nOPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\", \"\")\n\n# Anthropic (Claude) API key\nANTHROPIC_API_KEY = os.getenv(\"ANTHROPIC_API_KEY\", \"\")\n\n# Google AI (Gemini) API key\nGOOGLE_AI_API_KEY = os.getenv(\"GOOGLE_AI_API_KEY\", \"\")\n\n# Stability AI API key (image generation)\nSTABILITY_API_KEY = os.getenv(\"STABILITY_API_KEY\", \"\")\n\n# Replicate API key\nREPLICATE_API_KEY = os.getenv(\"REPLICATE_API_KEY\", \"\")\n\n# DeepSeek API key\nDEEPSEEK_API_KEY = os.getenv(\"DEEPSEEK_API_KEY\", \"\")\n\n# ============================================================================\n# FILE UPLOAD CONFIGURATION\n# ============================================================================\n\n# Maximum allowed file upload size in megabytes\nMAX_UPLOAD_SIZE_MB = int(os.getenv(\"MAX_UPLOAD_SIZE_MB\", \"50\"))\nMAX_UPLOAD_SIZE_BYTES = MAX_UPLOAD_SIZE_MB * 1024 * 1024\n\n# Allowed file extensions for upload\nALLOWED_UPLOAD_EXTENSIONS = [\n # Documents\n \".pdf\", \".docx\", \".txt\", \".rtf\", \".odt\",\n # Spreadsheets\n \".csv\", \".xlsx\", \".ods\",\n # Presentations\n \".pptx\", \".odp\",\n # Images\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\",\n # Code\n \".py\", \".js\", \".html\", \".css\", \".json\", \".xml\", \".yaml\", \".yml\",\n # Archives\n \".zip\", \".tar\", \".gz\",\n]\n\n# Upload storage directory\nUPLOAD_DIRECTORY = BASE_DIR / \"uploads\"\n\n# Temporary files directory (cleaned periodically)\nTEMP_DIRECTORY = BASE_DIR / \"temp\"\n\n# ============================================================================\n# EXPORT CONFIGURATION\n# ============================================================================\n\n# Export output directory\nEXPORT_DIRECTORY = BASE_DIR / \"exports\"\n\n# Supported export formats\nEXPORT_FORMATS = [\"pdf\", \"pptx\", \"docx\", \"txt\", \"json\", \"csv\", \"markdown\", \"html\"]\n\n# ============================================================================\n# LOGGING CONFIGURATION\n# ============================================================================\n\n# Log level: DEBUG | INFO | WARNING | ERROR | CRITICAL\nLOG_LEVEL = os.getenv(\"LOG_LEVEL\", \"INFO\")\n\n# Log message format\nLOG_FORMAT = (\n \"%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s\"\n)\n\n# Log file path\nLOG_FILE = BASE_DIR / \"logs\" / \"graphico.log\"\n\n# Log rotation: maximum file size before rotation (10 MB)\nLOG_MAX_BYTES = int(os.getenv(\"LOG_MAX_BYTES\", str(10 * 1024 * 1024)))\n\n# Log rotation: number of backup files to keep\nLOG_BACKUP_COUNT = int(os.getenv(\"LOG_BACKUP_COUNT\", \"5\"))\n\n# ============================================================================\n# RATE LIMITING\n# ============================================================================\n\n# Enable or disable rate limiting\nRATE_LIMIT_ENABLED = os.getenv(\"RATE_LIMIT_ENABLED\", \"True\").lower() == \"true\"\n\n# Number of requests allowed per time window\nRATE_LIMIT_REQUESTS = int(os.getenv(\"RATE_LIMIT_REQUESTS\", \"100\"))\n\n# Time window in seconds\nRATE_LIMIT_WINDOW_SECONDS = int(os.getenv(\"RATE_LIMIT_WINDOW_SECONDS\", \"60\"))\n\n# ============================================================================\n# CACHE CONFIGURATION\n# ============================================================================\n\n# Enable or disable caching\nCACHE_ENABLED = os.getenv(\"CACHE_ENABLED\", \"True\").lower() == \"true\"\n\n# Cache time-to-live in seconds (default: 5 minutes)\nCACHE_TTL_SECONDS = int(os.getenv(\"CACHE_TTL_SECONDS\", \"300\"))\n\n# Maximum number of cache entries\nCACHE_MAX_SIZE = int(os.getenv(\"CACHE_MAX_SIZE\", \"1000\"))\n\n# ============================================================================\n# SECURITY CONFIGURATION\n# ============================================================================\n\n# Bcrypt hashing rounds for password hashing (if needed in future)\nBCRYPT_ROUNDS = 12\n\n# Enable CSRF protection\nCSRF_ENABLED = os.getenv(\"CSRF_ENABLED\", \"True\").lower() == \"true\"\n\n# Maximum request body size (10 MB)\nMAX_REQUEST_SIZE_BYTES = int(os.getenv(\n \"MAX_REQUEST_SIZE_BYTES\",\n str(10 * 1024 * 1024)\n))\n\n# Request timeout in seconds\nREQUEST_TIMEOUT_SECONDS = int(os.getenv(\"REQUEST_TIMEOUT_SECONDS\", \"30\"))\n\n# ============================================================================\n# BUSINESS LOGIC CONSTANTS\n# ============================================================================\n\n# Maximum number of projects per user\nMAX_PROJECTS_PER_USER = int(os.getenv(\"MAX_PROJECTS_PER_USER\", \"50\"))\n\n# Maximum number of files per project\nMAX_FILES_PER_PROJECT = int(os.getenv(\"MAX_FILES_PER_PROJECT\", \"100\"))\n\n# Maximum chat history messages per project\nMAX_CHAT_HISTORY_LENGTH = int(os.getenv(\"MAX_CHAT_HISTORY_LENGTH\", \"500\"))\n\n# Maximum PDF file size for analysis in megabytes\nMAX_PDF_SIZE_MB = int(os.getenv(\"MAX_PDF_SIZE_MB\", \"25\"))\n\n# Maximum image generation resolution\nMAX_IMAGE_GENERATION_RESOLUTION = os.getenv(\n \"MAX_IMAGE_GENERATION_RESOLUTION\",\n \"2048x2048\"\n)\n\n# Maximum AI prompt length in characters\nMAX_PROMPT_LENGTH = int(os.getenv(\"MAX_PROMPT_LENGTH\", \"4000\"))\n\n# ============================================================================\n# DIRECTORY MANAGEMENT\n# ============================================================================\n\n# List of all directories that must exist for the application to function\nREQUIRED_DIRECTORIES = [\n DATABASE_PATH,\n UPLOAD_DIRECTORY,\n TEMP_DIRECTORY,\n EXPORT_DIRECTORY,\n BASE_DIR / \"logs\",\n BASE_DIR / \"models\",\n]\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef get_database_config() -> dict:\n \"\"\"\n Return the current database configuration as a dictionary.\n \n Returns:\n Dictionary with database type and connection details\n \"\"\"\n config = {\n \"type\": DATABASE_TYPE,\n \"path\": str(DATABASE_PATH),\n }\n \n if DATABASE_TYPE == \"json\":\n config[\"json\"] = {\n \"users_file\": str(JSON_USERS_FILE),\n \"projects_file\": str(JSON_PROJECTS_FILE),\n \"history_file\": str(JSON_HISTORY_FILE),\n \"sessions_file\": str(JSON_SESSIONS_FILE),\n \"oauth_states_file\": str(JSON_OAUTH_STATES_FILE),\n }\n elif DATABASE_TYPE == \"sqlite\":\n config[\"sqlite\"] = {\n \"path\": str(SQLITE_DATABASE_PATH),\n }\n elif DATABASE_TYPE == \"postgresql\":\n config[\"postgresql\"] = {\n \"host\": POSTGRES_HOST,\n \"port\": POSTGRES_PORT,\n \"database\": POSTGRES_DATABASE,\n \"user\": POSTGRES_USER,\n \"password_set\": bool(POSTGRES_PASSWORD),\n }\n \n return config\n\n\ndef get_ai_providers() -> dict:\n \"\"\"\n Return available AI providers and their configuration status.\n \n Returns:\n Dictionary mapping provider names to boolean (configured or not)\n \"\"\"\n return {\n \"github_models\": bool(GITHUB_MODELS_API_KEY),\n \"deepseek\": bool(DEEPSEEK_API_KEY),\n \"openai\": bool(OPENAI_API_KEY),\n \"anthropic\": bool(ANTHROPIC_API_KEY),\n \"google_ai\": bool(GOOGLE_AI_API_KEY),\n \"stability\": bool(STABILITY_API_KEY),\n \"replicate\": bool(REPLICATE_API_KEY),\n }\n\n\ndef get_configured_ai_providers() -> list:\n \"\"\"\n Return list of configured AI provider names.\n \n Returns:\n List of provider name strings that have API keys set\n \"\"\"\n providers = get_ai_providers()\n return [name for name, configured in providers.items() if configured]\n\n\ndef ensure_directories_exist() -> bool:\n \"\"\"\n Create all required directories if they don't exist.\n \n Returns:\n True if all directories exist or were created successfully,\n False if any directory creation failed\n \"\"\"\n all_ok = True\n \n for directory in REQUIRED_DIRECTORIES:\n try:\n directory.mkdir(parents=True, exist_ok=True)\n except OSError as e:\n print(f\"ERROR: Failed to create directory {directory}: {e}\")\n all_ok = False\n \n # Also ensure .gitkeep files exist to track empty directories\n for directory in REQUIRED_DIRECTORIES:\n gitkeep = directory / \".gitkeep\"\n if not gitkeep.exists():\n try:\n gitkeep.touch()\n except OSError:\n pass # Not critical\n \n return all_ok\n\n\ndef is_production() -> bool:\n \"\"\"\n Check if the server is running in production mode.\n \n Returns:\n True if SERVER_DEBUG is False (production), False otherwise\n \"\"\"\n return not SERVER_DEBUG\n\n\ndef validate_configuration() -> list:\n \"\"\"\n Validate the current configuration and return a list of issues.\n \n Checks for:\n - Missing or default secret keys (critical in production)\n - Missing API credentials\n - Directory existence\n - Configuration consistency\n \n Returns:\n List of warning/error message strings\n Messages prefixed with \"ERROR:\" indicate critical issues\n Messages prefixed with \"WARNING:\" indicate non-critical issues\n \"\"\"\n issues = []\n \n # Check for default secret keys (critical in production)\n default_secrets = {\n \"FLASK_SECRET_KEY\": \"graphico-pro-flask-secret-key-DEV-ONLY-change-in-production\",\n \"SESSION_SECRET_KEY\": \"graphico-pro-session-secret-DEV-ONLY-change-in-production\",\n \"OAUTH_STATE_SECRET\": \"graphico-oauth-state-secret-DEV-ONLY-change-in-production\",\n }\n \n for key_name, default_value in default_secrets.items():\n current_value = globals().get(key_name, \"\")\n if current_value == default_value:\n if is_production():\n issues.append(\n f\"ERROR: {key_name} is using the default value. \"\n f\"This is a critical security risk in production.\"\n )\n else:\n issues.append(\n f\"WARNING: {key_name} is using the default value. \"\n f\"Change this before deploying to production.\"\n )\n \n # Check Google OAuth configuration\n if not GOOGLE_CLIENT_SECRET:\n if is_production():\n issues.append(\n \"ERROR: GOOGLE_CLIENT_SECRET is not set. \"\n \"Google OAuth will not work.\"\n )\n else:\n issues.append(\n \"WARNING: GOOGLE_CLIENT_SECRET is not set. \"\n \"Google OAuth will not work until configured.\"\n )\n \n if not GOOGLE_CLIENT_ID:\n issues.append(\"ERROR: GOOGLE_CLIENT_ID is not configured.\")\n \n # Check AI provider configuration\n providers = get_ai_providers()\n configured_providers = [name for name, ok in providers.items() if ok]\n \n if not configured_providers:\n issues.append(\n \"WARNING: No AI providers are configured. \"\n \"AI features (chat, image generation, etc.) will not work. \"\n \"Set at least one API key.\"\n )\n \n # Check directory existence\n missing_dirs = []\n for directory in REQUIRED_DIRECTORIES:\n if not directory.exists():\n missing_dirs.append(str(directory))\n \n if missing_dirs:\n issues.append(\n f\"WARNING: Some directories are missing and will be created: \"\n f\"{', '.join(missing_dirs)}\"\n )\n \n # Check database configuration\n if DATABASE_TYPE == \"postgresql\" and not POSTGRES_PASSWORD:\n issues.append(\n \"WARNING: PostgreSQL is selected but POSTGRES_PASSWORD is not set.\"\n )\n \n # Check session security in production\n if is_production():\n if not SESSION_COOKIE_SECURE:\n issues.append(\n \"WARNING: SESSION_COOKIE_SECURE is False. \"\n \"Session cookies will be sent over HTTP. \"\n \"Enable this in production with HTTPS.\"\n )\n \n return issues\n\n\ndef get_config_summary() -> dict:\n \"\"\"\n Get a summary of the current configuration for display purposes.\n Sensitive values are masked.\n \n Returns:\n Dictionary with configuration summary\n \"\"\"\n return {\n \"server\": {\n \"host\": SERVER_HOST,\n \"port\": SERVER_PORT,\n \"debug\": SERVER_DEBUG,\n \"production\": is_production(),\n },\n \"database\": {\n \"type\": DATABASE_TYPE,\n \"path\": str(DATABASE_PATH),\n },\n \"ai_providers\": get_ai_providers(),\n \"oauth\": {\n \"client_id_configured\": bool(GOOGLE_CLIENT_ID),\n \"client_secret_configured\": bool(GOOGLE_CLIENT_SECRET),\n \"redirect_uris\": GOOGLE_REDIRECT_URIS,\n },\n \"session\": {\n \"expiry_hours\": SESSION_EXPIRY_HOURS,\n \"cookie_secure\": SESSION_COOKIE_SECURE,\n \"cookie_samesite\": SESSION_COOKIE_SAMESITE,\n },\n \"uploads\": {\n \"max_size_mb\": MAX_UPLOAD_SIZE_MB,\n \"allowed_extensions_count\": len(ALLOWED_UPLOAD_EXTENSIONS),\n },\n \"logging\": {\n \"level\": LOG_LEVEL,\n \"file\": str(LOG_FILE),\n },\n \"rate_limiting\": {\n \"enabled\": RATE_LIMIT_ENABLED,\n \"requests_per_window\": RATE_LIMIT_REQUESTS,\n \"window_seconds\": RATE_LIMIT_WINDOW_SECONDS,\n },\n }\n\n\ndef print_configuration_report() -> None:\n \"\"\"\n Print a formatted configuration report to the console.\n Useful for debugging and deployment verification.\n \"\"\"\n summary = get_config_summary()\n configured_providers = get_configured_ai_providers()\n \n print(\"\\n\" + \"=\" * 60)\n print(\"GRAPHICO PRO - CONFIGURATION REPORT\")\n print(\"=\" * 60)\n print(f\" Environment: {'PRODUCTION' if is_production() else 'DEVELOPMENT'}\")\n print(f\" Server: {summary['server']['host']}:{summary['server']['port']}\")\n print(f\" Database: {summary['database']['type']}\")\n print(f\" AI Providers: {len(configured_providers)} configured\")\n for provider in configured_providers:\n print(f\" - {provider}\")\n print(f\" OAuth Client ID: {'Configured' if summary['oauth']['client_id_configured'] else 'MISSING'}\")\n print(f\" OAuth Secret: {'Configured' if summary['oauth']['client_secret_configured'] else 'MISSING'}\")\n print(f\" Session Expiry: {summary['session']['expiry_hours']} hours\")\n print(f\" Max Upload: {summary['uploads']['max_size_mb']} MB\")\n print(f\" Log Level: {summary['logging']['level']}\")\n print(\"=\" * 60)\n \n issues = validate_configuration()\n if issues:\n print(\"\\nCONFIGURATION ISSUES:\")\n for issue in issues:\n print(f\" {issue}\")\n print(\"\")\n"}, "4e00e5c213c9ed8d2817a7bfd00d11ff43bff9ae": {"t": 1785747667.0778704, "n": 22105, "h": "1623eaf5b7088478", "c": "\"\"\"\nGraphico Pro - Centralized Configuration Settings\nProduction-grade settings management with environment variable support.\n\nSECURITY WARNING:\n This file contains default values for development ONLY.\n In production, ALL secret keys and credentials MUST be set via\n environment variables. Never commit real secrets to version control.\n\nUsage:\n All settings can be overridden by setting environment variables\n before starting the server. Use the .env file for local development.\n\"\"\"\n\nimport os\nimport secrets\nfrom pathlib import Path\nfrom dotenv import load_dotenv\n\n# Load environment variables from .env file (if it exists)\n# .env should NEVER be committed to version control\nenv_path = Path(__file__).resolve().parent.parent / \".env\"\nif env_path.exists():\n load_dotenv(env_path)\nelse:\n # Try to load from current working directory as fallback\n load_dotenv()\n\n# ============================================================================\n# BASE DIRECTORY\n# ============================================================================\n\n# Absolute path to the project root directory\nBASE_DIR = Path(__file__).resolve().parent.parent\n\n# ============================================================================\n# SERVER CONFIGURATION\n# ============================================================================\n\nSERVER_HOST = os.getenv(\"SERVER_HOST\", \"localhost\")\nSERVER_PORT = int(os.getenv(\"SERVER_PORT\", \"8501\"))\n\n# WARNING: Never enable DEBUG in production\nSERVER_DEBUG = os.getenv(\"SERVER_DEBUG\", \"False\").lower() == \"true\"\n\n# Number of worker threads for the development server\nSERVER_WORKERS = int(os.getenv(\"SERVER_WORKERS\", \"4\"))\n\n# Secret key for Flask session signing\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nFLASK_SECRET_KEY = os.getenv(\n \"FLASK_SECRET_KEY\",\n \"graphico-pro-flask-secret-key-DEV-ONLY-change-in-production\"\n)\n\n# ============================================================================\n# CORS CONFIGURATION\n# ============================================================================\n\n# Origins allowed to make cross-origin requests to the API\nALLOWED_ORIGINS = [\n \"http://localhost:5500\",\n \"http://127.0.0.1:5500\",\n \"https://graphico.streamlit.app\",\n \"https://graphico-backend.streamlit.app\"\n]\n\n# HTTP methods allowed for CORS requests\nALLOWED_METHODS = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"]\n\n# HTTP headers allowed for CORS requests\nALLOWED_HEADERS = [\n \"Content-Type\",\n \"Authorization\",\n \"X-Requested-With\",\n \"X-Session-Token\",\n \"X-Request-ID\",\n]\n\n# Allow credentials (cookies, authorization headers) in CORS requests\nALLOWED_CREDENTIALS = True\n\n# ============================================================================\n# GOOGLE OAUTH CONFIGURATION\n# ============================================================================\n\n# Google OAuth 2.0 Client ID\n# This is public and can be in version control\nGOOGLE_CLIENT_ID = os.getenv(\n \"GOOGLE_CLIENT_ID\",\n \"578730974907-criqtv67ocspqdo84b568sj13tiqgv1t.apps.googleusercontent.com\"\n)\n\n# Google OAuth 2.0 Client Secret\n# WARNING: THIS MUST BE SET VIA ENVIRONMENT VARIABLE IN PRODUCTION\n# NEVER commit the actual secret to version control\n# Get it from: https://console.cloud.google.com/apis/credentials\nGOOGLE_CLIENT_SECRET = os.getenv(\"GOOGLE_CLIENT_SECRET\", \"\")\n\n# Allowed redirect URIs (must match Google Cloud Console configuration)\nGOOGLE_REDIRECT_URIS = [\n \"http://localhost:5500\",\n \"https://graphico.streamlit.app\",\n \"https://graphico-backend.streamlit.app\"\n]\n\n# Google OAuth 2.0 endpoints\nGOOGLE_TOKEN_URI = \"https://oauth2.googleapis.com/token\"\nGOOGLE_AUTH_URI = \"https://accounts.google.com/o/oauth2/auth\"\nGOOGLE_USERINFO_URI = \"https://www.googleapis.com/oauth2/v3/userinfo\"\nGOOGLE_CERTS_URI = \"https://www.googleapis.com/oauth2/v3/certs\"\n\n# OAuth scopes requested from Google\nGOOGLE_SCOPES = [\n \"openid\",\n \"https://www.googleapis.com/auth/userinfo.email\",\n \"https://www.googleapis.com/auth/userinfo.profile\",\n]\n\n# ============================================================================\n# CSRF / STATE PROTECTION\n# ============================================================================\n\n# Secret used to sign OAuth state tokens for CSRF protection\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nOAUTH_STATE_SECRET = os.getenv(\n \"OAUTH_STATE_SECRET\",\n \"graphico-oauth-state-secret-DEV-ONLY-change-in-production\"\n)\n\n# Maximum age of OAuth state tokens in seconds (10 minutes)\nOAUTH_STATE_MAX_AGE_SECONDS = int(os.getenv(\"OAUTH_STATE_MAX_AGE_SECONDS\", \"600\"))\n\n# ============================================================================\n# SESSION CONFIGURATION\n# ============================================================================\n\n# Secret key for signing session tokens\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nSESSION_SECRET_KEY = os.getenv(\n \"SESSION_SECRET_KEY\",\n \"graphico-pro-session-secret-DEV-ONLY-change-in-production\"\n)\n\n# Session expiration time in hours (default: 24 hours)\nSESSION_EXPIRY_HOURS = int(os.getenv(\"SESSION_EXPIRY_HOURS\", \"24\"))\n\n# Session cookie name\nSESSION_COOKIE_NAME = os.getenv(\"SESSION_COOKIE_NAME\", \"graphico_session\")\n\n# Cookie security settings\n# WARNING: Set SESSION_COOKIE_SECURE=True in production (HTTPS only)\nSESSION_COOKIE_SECURE = os.getenv(\"SESSION_COOKIE_SECURE\", \"False\").lower() == \"true\"\nSESSION_COOKIE_HTTPONLY = True # Always True - prevent JavaScript access\nSESSION_COOKIE_SAMESITE = os.getenv(\"SESSION_COOKIE_SAMESITE\", \"Lax\")\n\n# Maximum sessions per user (prevents session flooding)\nMAX_SESSIONS_PER_USER = int(os.getenv(\"MAX_SESSIONS_PER_USER\", \"10\"))\n\n# ============================================================================\n# DATABASE CONFIGURATION\n# ============================================================================\n\n# Database backend type: \"json\" | \"sqlite\" | \"postgresql\"\nDATABASE_TYPE = os.getenv(\"DATABASE_TYPE\", \"json\")\n\n# Base directory for all database files\nDATABASE_PATH = BASE_DIR / \"database\" / \"data\"\n\n# JSON Database file paths (current default)\nJSON_USERS_FILE = DATABASE_PATH / \"users.json\"\nJSON_PROJECTS_FILE = DATABASE_PATH / \"projects.json\"\nJSON_HISTORY_FILE = DATABASE_PATH / \"history.json\"\nJSON_SESSIONS_FILE = DATABASE_PATH / \"sessions.json\"\nJSON_OAUTH_STATES_FILE = DATABASE_PATH / \"oauth_states.json\"\n\n# SQLite configuration (future)\nSQLITE_DATABASE_PATH = DATABASE_PATH / \"graphico.db\"\n\n# PostgreSQL configuration (future)\nPOSTGRES_HOST = os.getenv(\"POSTGRES_HOST\", \"localhost\")\nPOSTGRES_PORT = int(os.getenv(\"POSTGRES_PORT\", \"5432\"))\nPOSTGRES_DATABASE = os.getenv(\"POSTGRES_DATABASE\", \"graphico\")\nPOSTGRES_USER = os.getenv(\"POSTGRES_USER\", \"graphico_user\")\n# WARNING: NEVER set a default PostgreSQL password - must be set via env\nPOSTGRES_PASSWORD = os.getenv(\"POSTGRES_PASSWORD\", \"\")\n\n# ============================================================================\n# AI PROVIDER CONFIGURATION\n# ============================================================================\n\n# WARNING: API keys must ALWAYS be set via environment variables\n# Never hardcode API keys or commit them to version control\n\n# GitHub Models (free GPT-4o via Azure)\nGITHUB_MODELS_API_KEY = os.getenv(\"GITHUB_MODELS_API_KEY\", \"\")\n\n# OpenAI API key\nOPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\", \"\")\n\n# Anthropic (Claude) API key\nANTHROPIC_API_KEY = os.getenv(\"ANTHROPIC_API_KEY\", \"\")\n\n# Google AI (Gemini) API key\nGOOGLE_AI_API_KEY = os.getenv(\"GOOGLE_AI_API_KEY\", \"\")\n\n# Stability AI API key (image generation)\nSTABILITY_API_KEY = os.getenv(\"STABILITY_API_KEY\", \"\")\n\n# Replicate API key\nREPLICATE_API_KEY = os.getenv(\"REPLICATE_API_KEY\", \"\")\n\n# DeepSeek API key\nDEEPSEEK_API_KEY = os.getenv(\"DEEPSEEK_API_KEY\", \"\")\n\n# ============================================================================\n# FILE UPLOAD CONFIGURATION\n# ============================================================================\n\n# Maximum allowed file upload size in megabytes\nMAX_UPLOAD_SIZE_MB = int(os.getenv(\"MAX_UPLOAD_SIZE_MB\", \"50\"))\nMAX_UPLOAD_SIZE_BYTES = MAX_UPLOAD_SIZE_MB * 1024 * 1024\n\n# Allowed file extensions for upload\nALLOWED_UPLOAD_EXTENSIONS = [\n # Documents\n \".pdf\", \".docx\", \".txt\", \".rtf\", \".odt\",\n # Spreadsheets\n \".csv\", \".xlsx\", \".ods\",\n # Presentations\n \".pptx\", \".odp\",\n # Images\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\",\n # Code\n \".py\", \".js\", \".html\", \".css\", \".json\", \".xml\", \".yaml\", \".yml\",\n # Archives\n \".zip\", \".tar\", \".gz\",\n]\n\n# Upload storage directory\nUPLOAD_DIRECTORY = BASE_DIR / \"uploads\"\n\n# Temporary files directory (cleaned periodically)\nTEMP_DIRECTORY = BASE_DIR / \"temp\"\n\n# ============================================================================\n# EXPORT CONFIGURATION\n# ============================================================================\n\n# Export output directory\nEXPORT_DIRECTORY = BASE_DIR / \"exports\"\n\n# Supported export formats\nEXPORT_FORMATS = [\"pdf\", \"pptx\", \"docx\", \"txt\", \"json\", \"csv\", \"markdown\", \"html\"]\n\n# ============================================================================\n# LOGGING CONFIGURATION\n# ============================================================================\n\n# Log level: DEBUG | INFO | WARNING | ERROR | CRITICAL\nLOG_LEVEL = os.getenv(\"LOG_LEVEL\", \"INFO\")\n\n# Log message format\nLOG_FORMAT = (\n \"%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s\"\n)\n\n# Log file path\nLOG_FILE = BASE_DIR / \"logs\" / \"graphico.log\"\n\n# Log rotation: maximum file size before rotation (10 MB)\nLOG_MAX_BYTES = int(os.getenv(\"LOG_MAX_BYTES\", str(10 * 1024 * 1024)))\n\n# Log rotation: number of backup files to keep\nLOG_BACKUP_COUNT = int(os.getenv(\"LOG_BACKUP_COUNT\", \"5\"))\n\n# ============================================================================\n# RATE LIMITING\n# ============================================================================\n\n# Enable or disable rate limiting\nRATE_LIMIT_ENABLED = os.getenv(\"RATE_LIMIT_ENABLED\", \"True\").lower() == \"true\"\n\n# Number of requests allowed per time window\nRATE_LIMIT_REQUESTS = int(os.getenv(\"RATE_LIMIT_REQUESTS\", \"100\"))\n\n# Time window in seconds\nRATE_LIMIT_WINDOW_SECONDS = int(os.getenv(\"RATE_LIMIT_WINDOW_SECONDS\", \"60\"))\n\n# ============================================================================\n# CACHE CONFIGURATION\n# ============================================================================\n\n# Enable or disable caching\nCACHE_ENABLED = os.getenv(\"CACHE_ENABLED\", \"True\").lower() == \"true\"\n\n# Cache time-to-live in seconds (default: 5 minutes)\nCACHE_TTL_SECONDS = int(os.getenv(\"CACHE_TTL_SECONDS\", \"300\"))\n\n# Maximum number of cache entries\nCACHE_MAX_SIZE = int(os.getenv(\"CACHE_MAX_SIZE\", \"1000\"))\n\n# ============================================================================\n# SECURITY CONFIGURATION\n# ============================================================================\n\n# Bcrypt hashing rounds for password hashing (if needed in future)\nBCRYPT_ROUNDS = 12\n\n# Enable CSRF protection\nCSRF_ENABLED = os.getenv(\"CSRF_ENABLED\", \"True\").lower() == \"true\"\n\n# Maximum request body size (10 MB)\nMAX_REQUEST_SIZE_BYTES = int(os.getenv(\n \"MAX_REQUEST_SIZE_BYTES\",\n str(10 * 1024 * 1024)\n))\n\n# Request timeout in seconds\nREQUEST_TIMEOUT_SECONDS = int(os.getenv(\"REQUEST_TIMEOUT_SECONDS\", \"30\"))\n\n# ============================================================================\n# BUSINESS LOGIC CONSTANTS\n# ============================================================================\n\n# Maximum number of projects per user\nMAX_PROJECTS_PER_USER = int(os.getenv(\"MAX_PROJECTS_PER_USER\", \"50\"))\n\n# Maximum number of files per project\nMAX_FILES_PER_PROJECT = int(os.getenv(\"MAX_FILES_PER_PROJECT\", \"100\"))\n\n# Maximum chat history messages per project\nMAX_CHAT_HISTORY_LENGTH = int(os.getenv(\"MAX_CHAT_HISTORY_LENGTH\", \"500\"))\n\n# Maximum PDF file size for analysis in megabytes\nMAX_PDF_SIZE_MB = int(os.getenv(\"MAX_PDF_SIZE_MB\", \"25\"))\n\n# Maximum image generation resolution\nMAX_IMAGE_GENERATION_RESOLUTION = os.getenv(\n \"MAX_IMAGE_GENERATION_RESOLUTION\",\n \"2048x2048\"\n)\n\n# Maximum AI prompt length in characters\nMAX_PROMPT_LENGTH = int(os.getenv(\"MAX_PROMPT_LENGTH\", \"4000\"))\n\n# ============================================================================\n# DIRECTORY MANAGEMENT\n# ============================================================================\n\n# List of all directories that must exist for the application to function\nREQUIRED_DIRECTORIES = [\n DATABASE_PATH,\n UPLOAD_DIRECTORY,\n TEMP_DIRECTORY,\n EXPORT_DIRECTORY,\n BASE_DIR / \"logs\",\n BASE_DIR / \"models\",\n]\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef get_database_config() -> dict:\n \"\"\"\n Return the current database configuration as a dictionary.\n \n Returns:\n Dictionary with database type and connection details\n \"\"\"\n config = {\n \"type\": DATABASE_TYPE,\n \"path\": str(DATABASE_PATH),\n }\n \n if DATABASE_TYPE == \"json\":\n config[\"json\"] = {\n \"users_file\": str(JSON_USERS_FILE),\n \"projects_file\": str(JSON_PROJECTS_FILE),\n \"history_file\": str(JSON_HISTORY_FILE),\n \"sessions_file\": str(JSON_SESSIONS_FILE),\n \"oauth_states_file\": str(JSON_OAUTH_STATES_FILE),\n }\n elif DATABASE_TYPE == \"sqlite\":\n config[\"sqlite\"] = {\n \"path\": str(SQLITE_DATABASE_PATH),\n }\n elif DATABASE_TYPE == \"postgresql\":\n config[\"postgresql\"] = {\n \"host\": POSTGRES_HOST,\n \"port\": POSTGRES_PORT,\n \"database\": POSTGRES_DATABASE,\n \"user\": POSTGRES_USER,\n \"password_set\": bool(POSTGRES_PASSWORD),\n }\n \n return config\n\n\ndef get_ai_providers() -> dict:\n \"\"\"\n Return available AI providers and their configuration status.\n \n Returns:\n Dictionary mapping provider names to boolean (configured or not)\n \"\"\"\n return {\n \"github_models\": bool(GITHUB_MODELS_API_KEY),\n \"deepseek\": bool(DEEPSEEK_API_KEY),\n \"openai\": bool(OPENAI_API_KEY),\n \"anthropic\": bool(ANTHROPIC_API_KEY),\n \"google_ai\": bool(GOOGLE_AI_API_KEY),\n \"stability\": bool(STABILITY_API_KEY),\n \"replicate\": bool(REPLICATE_API_KEY),\n }\n\n\ndef get_configured_ai_providers() -> list:\n \"\"\"\n Return list of configured AI provider names.\n \n Returns:\n List of provider name strings that have API keys set\n \"\"\"\n providers = get_ai_providers()\n return [name for name, configured in providers.items() if configured]\n\n\ndef ensure_directories_exist() -> bool:\n \"\"\"\n Create all required directories if they don't exist.\n \n Returns:\n True if all directories exist or were created successfully,\n False if any directory creation failed\n \"\"\"\n all_ok = True\n \n for directory in REQUIRED_DIRECTORIES:\n try:\n directory.mkdir(parents=True, exist_ok=True)\n except OSError as e:\n print(f\"ERROR: Failed to create directory {directory}: {e}\")\n all_ok = False\n \n # Also ensure .gitkeep files exist to track empty directories\n for directory in REQUIRED_DIRECTORIES:\n gitkeep = directory / \".gitkeep\"\n if not gitkeep.exists():\n try:\n gitkeep.touch()\n except OSError:\n pass # Not critical\n \n return all_ok\n\n\ndef is_production() -> bool:\n \"\"\"\n Check if the server is running in production mode.\n \n Returns:\n True if SERVER_DEBUG is False (production), False otherwise\n \"\"\"\n return not SERVER_DEBUG\n\n\ndef validate_configuration() -> list:\n \"\"\"\n Validate the current configuration and return a list of issues.\n \n Checks for:\n - Missing or default secret keys (critical in production)\n - Missing API credentials\n - Directory existence\n - Configuration consistency\n \n Returns:\n List of warning/error message strings\n Messages prefixed with \"ERROR:\" indicate critical issues\n Messages prefixed with \"WARNING:\" indicate non-critical issues\n \"\"\"\n issues = []\n \n # Check for default secret keys (critical in production)\n default_secrets = {\n \"FLASK_SECRET_KEY\": \"graphico-pro-flask-secret-key-DEV-ONLY-change-in-production\",\n \"SESSION_SECRET_KEY\": \"graphico-pro-session-secret-DEV-ONLY-change-in-production\",\n \"OAUTH_STATE_SECRET\": \"graphico-oauth-state-secret-DEV-ONLY-change-in-production\",\n }\n \n for key_name, default_value in default_secrets.items():\n current_value = globals().get(key_name, \"\")\n if current_value == default_value:\n if is_production():\n issues.append(\n f\"ERROR: {key_name} is using the default value. \"\n f\"This is a critical security risk in production.\"\n )\n else:\n issues.append(\n f\"WARNING: {key_name} is using the default value. \"\n f\"Change this before deploying to production.\"\n )\n \n # Check Google OAuth configuration\n if not GOOGLE_CLIENT_SECRET:\n if is_production():\n issues.append(\n \"ERROR: GOOGLE_CLIENT_SECRET is not set. \"\n \"Google OAuth will not work.\"\n )\n else:\n issues.append(\n \"WARNING: GOOGLE_CLIENT_SECRET is not set. \"\n \"Google OAuth will not work until configured.\"\n )\n \n if not GOOGLE_CLIENT_ID:\n issues.append(\"ERROR: GOOGLE_CLIENT_ID is not configured.\")\n \n # Check AI provider configuration\n providers = get_ai_providers()\n configured_providers = [name for name, ok in providers.items() if ok]\n \n if not configured_providers:\n issues.append(\n \"WARNING: No AI providers are configured. \"\n \"AI features (chat, image generation, etc.) will not work. \"\n \"Set at least one API key.\"\n )\n \n # Check directory existence\n missing_dirs = []\n for directory in REQUIRED_DIRECTORIES:\n if not directory.exists():\n missing_dirs.append(str(directory))\n \n if missing_dirs:\n issues.append(\n f\"WARNING: Some directories are missing and will be created: \"\n f\"{', '.join(missing_dirs)}\"\n )\n \n # Check database configuration\n if DATABASE_TYPE == \"postgresql\" and not POSTGRES_PASSWORD:\n issues.append(\n \"WARNING: PostgreSQL is selected but POSTGRES_PASSWORD is not set.\"\n )\n \n # Check session security in production\n if is_production():\n if not SESSION_COOKIE_SECURE:\n issues.append(\n \"WARNING: SESSION_COOKIE_SECURE is False. \"\n \"Session cookies will be sent over HTTP. \"\n \"Enable this in production with HTTPS.\"\n )\n \n return issues\n\n\ndef get_config_summary() -> dict:\n \"\"\"\n Get a summary of the current configuration for display purposes.\n Sensitive values are masked.\n \n Returns:\n Dictionary with configuration summary\n \"\"\"\n return {\n \"server\": {\n \"host\": SERVER_HOST,\n \"port\": SERVER_PORT,\n \"debug\": SERVER_DEBUG,\n \"production\": is_production(),\n },\n \"database\": {\n \"type\": DATABASE_TYPE,\n \"path\": str(DATABASE_PATH),\n },\n \"ai_providers\": get_ai_providers(),\n \"oauth\": {\n \"client_id_configured\": bool(GOOGLE_CLIENT_ID),\n \"client_secret_configured\": bool(GOOGLE_CLIENT_SECRET),\n \"redirect_uris\": GOOGLE_REDIRECT_URIS,\n },\n \"session\": {\n \"expiry_hours\": SESSION_EXPIRY_HOURS,\n \"cookie_secure\": SESSION_COOKIE_SECURE,\n \"cookie_samesite\": SESSION_COOKIE_SAMESITE,\n },\n \"uploads\": {\n \"max_size_mb\": MAX_UPLOAD_SIZE_MB,\n \"allowed_extensions_count\": len(ALLOWED_UPLOAD_EXTENSIONS),\n },\n \"logging\": {\n \"level\": LOG_LEVEL,\n \"file\": str(LOG_FILE),\n },\n \"rate_limiting\": {\n \"enabled\": RATE_LIMIT_ENABLED,\n \"requests_per_window\": RATE_LIMIT_REQUESTS,\n \"window_seconds\": RATE_LIMIT_WINDOW_SECONDS,\n },\n }\n\n\ndef print_configuration_report() -> None:\n \"\"\"\n Print a formatted configuration report to the console.\n Useful for debugging and deployment verification.\n \"\"\"\n summary = get_config_summary()\n configured_providers = get_configured_ai_providers()\n \n print(\"\\n\" + \"=\" * 60)\n print(\"GRAPHICO PRO - CONFIGURATION REPORT\")\n print(\"=\" * 60)\n print(f\" Environment: {'PRODUCTION' if is_production() else 'DEVELOPMENT'}\")\n print(f\" Server: {summary['server']['host']}:{summary['server']['port']}\")\n print(f\" Database: {summary['database']['type']}\")\n print(f\" AI Providers: {len(configured_providers)} configured\")\n for provider in configured_providers:\n print(f\" - {provider}\")\n print(f\" OAuth Client ID: {'Configured' if summary['oauth']['client_id_configured'] else 'MISSING'}\")\n print(f\" OAuth Secret: {'Configured' if summary['oauth']['client_secret_configured'] else 'MISSING'}\")\n print(f\" Session Expiry: {summary['session']['expiry_hours']} hours\")\n print(f\" Max Upload: {summary['uploads']['max_size_mb']} MB\")\n print(f\" Log Level: {summary['logging']['level']}\")\n print(\"=\" * 60)\n \n issues = validate_configuration()\n if issues:\n print(\"\\nCONFIGURATION ISSUES:\")\n for issue in issues:\n print(f\" {issue}\")\n print(\"\")\n"}, "d324ce790061dad8b18470fdfdffd917fef0031b": {"t": 1785747668.436682, "n": 22014, "h": "c54b7d15c6471653", "c": "\"\"\"\nGraphico Pro - Centralized Configuration Settings\nProduction-grade settings management with environment variable support.\n\nSECURITY WARNING:\n This file contains default values for development ONLY.\n In production, ALL secret keys and credentials MUST be set via\n environment variables. Never commit real secrets to version control.\n\nUsage:\n All settings can be overridden by setting environment variables\n before starting the server. Use the .env file for local development.\n\"\"\"\n\nimport os\nimport secrets\nfrom pathlib import Path\nfrom dotenv import load_dotenv\n\n# Load environment variables from .env file (if it exists)\n# .env should NEVER be committed to version control\nenv_path = Path(__file__).resolve().parent.parent / \".env\"\nif env_path.exists():\n load_dotenv(env_path)\nelse:\n # Try to load from current working directory as fallback\n load_dotenv()\n\n# ============================================================================\n# BASE DIRECTORY\n# ============================================================================\n\n# Absolute path to the project root directory\nBASE_DIR = Path(__file__).resolve().parent.parent\n\n# ============================================================================\n# SERVER CONFIGURATION\n# ============================================================================\n\nSERVER_HOST = os.getenv(\"SERVER_HOST\", \"localhost\")\nSERVER_PORT = int(os.getenv(\"SERVER_PORT\", \"8501\"))\n\n# WARNING: Never enable DEBUG in production\nSERVER_DEBUG = os.getenv(\"SERVER_DEBUG\", \"False\").lower() == \"true\"\n\n# Number of worker threads for the development server\nSERVER_WORKERS = int(os.getenv(\"SERVER_WORKERS\", \"4\"))\n\n# Secret key for Flask session signing\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nFLASK_SECRET_KEY = os.getenv(\n \"FLASK_SECRET_KEY\",\n \"graphico-pro-flask-secret-key-DEV-ONLY-change-in-production\"\n)\n\n# ============================================================================\n# CORS CONFIGURATION\n# ============================================================================\n\n# Origins allowed to make cross-origin requests to the API\nALLOWED_ORIGINS = [\n \"http://localhost:5500\",\n \"http://127.0.0.1:5500\",\n \"https://graphico.streamlit.app\",\n]\n\n# HTTP methods allowed for CORS requests\nALLOWED_METHODS = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"]\n\n# HTTP headers allowed for CORS requests\nALLOWED_HEADERS = [\n \"Content-Type\",\n \"Authorization\",\n \"X-Requested-With\",\n \"X-Session-Token\",\n \"X-Request-ID\",\n]\n\n# Allow credentials (cookies, authorization headers) in CORS requests\nALLOWED_CREDENTIALS = True\n\n# ============================================================================\n# GOOGLE OAUTH CONFIGURATION\n# ============================================================================\n\n# Google OAuth 2.0 Client ID\n# This is public and can be in version control\nGOOGLE_CLIENT_ID = os.getenv(\n \"GOOGLE_CLIENT_ID\",\n \"578730974907-criqtv67ocspqdo84b568sj13tiqgv1t.apps.googleusercontent.com\"\n)\n\n# Google OAuth 2.0 Client Secret\n# WARNING: THIS MUST BE SET VIA ENVIRONMENT VARIABLE IN PRODUCTION\n# NEVER commit the actual secret to version control\n# Get it from: https://console.cloud.google.com/apis/credentials\nGOOGLE_CLIENT_SECRET = os.getenv(\"GOOGLE_CLIENT_SECRET\", \"\")\n\n# Allowed redirect URIs (must match Google Cloud Console configuration)\nGOOGLE_REDIRECT_URIS = [\n \"http://localhost:5500\",\n \"https://graphico.streamlit.app\",\n]\n\n# Google OAuth 2.0 endpoints\nGOOGLE_TOKEN_URI = \"https://oauth2.googleapis.com/token\"\nGOOGLE_AUTH_URI = \"https://accounts.google.com/o/oauth2/auth\"\nGOOGLE_USERINFO_URI = \"https://www.googleapis.com/oauth2/v3/userinfo\"\nGOOGLE_CERTS_URI = \"https://www.googleapis.com/oauth2/v3/certs\"\n\n# OAuth scopes requested from Google\nGOOGLE_SCOPES = [\n \"openid\",\n \"https://www.googleapis.com/auth/userinfo.email\",\n \"https://www.googleapis.com/auth/userinfo.profile\",\n]\n\n# ============================================================================\n# CSRF / STATE PROTECTION\n# ============================================================================\n\n# Secret used to sign OAuth state tokens for CSRF protection\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nOAUTH_STATE_SECRET = os.getenv(\n \"OAUTH_STATE_SECRET\",\n \"graphico-oauth-state-secret-DEV-ONLY-change-in-production\"\n)\n\n# Maximum age of OAuth state tokens in seconds (10 minutes)\nOAUTH_STATE_MAX_AGE_SECONDS = int(os.getenv(\"OAUTH_STATE_MAX_AGE_SECONDS\", \"600\"))\n\n# ============================================================================\n# SESSION CONFIGURATION\n# ============================================================================\n\n# Secret key for signing session tokens\n# WARNING: CHANGE THIS IN PRODUCTION - Use a strong random value\n# Generate with: python -c \"import secrets; print(secrets.token_hex(32))\"\nSESSION_SECRET_KEY = os.getenv(\n \"SESSION_SECRET_KEY\",\n \"graphico-pro-session-secret-DEV-ONLY-change-in-production\"\n)\n\n# Session expiration time in hours (default: 24 hours)\nSESSION_EXPIRY_HOURS = int(os.getenv(\"SESSION_EXPIRY_HOURS\", \"24\"))\n\n# Session cookie name\nSESSION_COOKIE_NAME = os.getenv(\"SESSION_COOKIE_NAME\", \"graphico_session\")\n\n# Cookie security settings\n# WARNING: Set SESSION_COOKIE_SECURE=True in production (HTTPS only)\nSESSION_COOKIE_SECURE = os.getenv(\"SESSION_COOKIE_SECURE\", \"False\").lower() == \"true\"\nSESSION_COOKIE_HTTPONLY = True # Always True - prevent JavaScript access\nSESSION_COOKIE_SAMESITE = os.getenv(\"SESSION_COOKIE_SAMESITE\", \"Lax\")\n\n# Maximum sessions per user (prevents session flooding)\nMAX_SESSIONS_PER_USER = int(os.getenv(\"MAX_SESSIONS_PER_USER\", \"10\"))\n\n# ============================================================================\n# DATABASE CONFIGURATION\n# ============================================================================\n\n# Database backend type: \"json\" | \"sqlite\" | \"postgresql\"\nDATABASE_TYPE = os.getenv(\"DATABASE_TYPE\", \"json\")\n\n# Base directory for all database files\nDATABASE_PATH = BASE_DIR / \"database\" / \"data\"\n\n# JSON Database file paths (current default)\nJSON_USERS_FILE = DATABASE_PATH / \"users.json\"\nJSON_PROJECTS_FILE = DATABASE_PATH / \"projects.json\"\nJSON_HISTORY_FILE = DATABASE_PATH / \"history.json\"\nJSON_SESSIONS_FILE = DATABASE_PATH / \"sessions.json\"\nJSON_OAUTH_STATES_FILE = DATABASE_PATH / \"oauth_states.json\"\n\n# SQLite configuration (future)\nSQLITE_DATABASE_PATH = DATABASE_PATH / \"graphico.db\"\n\n# PostgreSQL configuration (future)\nPOSTGRES_HOST = os.getenv(\"POSTGRES_HOST\", \"localhost\")\nPOSTGRES_PORT = int(os.getenv(\"POSTGRES_PORT\", \"5432\"))\nPOSTGRES_DATABASE = os.getenv(\"POSTGRES_DATABASE\", \"graphico\")\nPOSTGRES_USER = os.getenv(\"POSTGRES_USER\", \"graphico_user\")\n# WARNING: NEVER set a default PostgreSQL password - must be set via env\nPOSTGRES_PASSWORD = os.getenv(\"POSTGRES_PASSWORD\", \"\")\n\n# ============================================================================\n# AI PROVIDER CONFIGURATION\n# ============================================================================\n\n# WARNING: API keys must ALWAYS be set via environment variables\n# Never hardcode API keys or commit them to version control\n\n# GitHub Models (free GPT-4o via Azure)\nGITHUB_MODELS_API_KEY = os.getenv(\"GITHUB_MODELS_API_KEY\", \"\")\n\n# OpenAI API key\nOPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\", \"\")\n\n# Anthropic (Claude) API key\nANTHROPIC_API_KEY = os.getenv(\"ANTHROPIC_API_KEY\", \"\")\n\n# Google AI (Gemini) API key\nGOOGLE_AI_API_KEY = os.getenv(\"GOOGLE_AI_API_KEY\", \"\")\n\n# Stability AI API key (image generation)\nSTABILITY_API_KEY = os.getenv(\"STABILITY_API_KEY\", \"\")\n\n# Replicate API key\nREPLICATE_API_KEY = os.getenv(\"REPLICATE_API_KEY\", \"\")\n\n# DeepSeek API key\nDEEPSEEK_API_KEY = os.getenv(\"DEEPSEEK_API_KEY\", \"\")\n\n# ============================================================================\n# FILE UPLOAD CONFIGURATION\n# ============================================================================\n\n# Maximum allowed file upload size in megabytes\nMAX_UPLOAD_SIZE_MB = int(os.getenv(\"MAX_UPLOAD_SIZE_MB\", \"50\"))\nMAX_UPLOAD_SIZE_BYTES = MAX_UPLOAD_SIZE_MB * 1024 * 1024\n\n# Allowed file extensions for upload\nALLOWED_UPLOAD_EXTENSIONS = [\n # Documents\n \".pdf\", \".docx\", \".txt\", \".rtf\", \".odt\",\n # Spreadsheets\n \".csv\", \".xlsx\", \".ods\",\n # Presentations\n \".pptx\", \".odp\",\n # Images\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\",\n # Code\n \".py\", \".js\", \".html\", \".css\", \".json\", \".xml\", \".yaml\", \".yml\",\n # Archives\n \".zip\", \".tar\", \".gz\",\n]\n\n# Upload storage directory\nUPLOAD_DIRECTORY = BASE_DIR / \"uploads\"\n\n# Temporary files directory (cleaned periodically)\nTEMP_DIRECTORY = BASE_DIR / \"temp\"\n\n# ============================================================================\n# EXPORT CONFIGURATION\n# ============================================================================\n\n# Export output directory\nEXPORT_DIRECTORY = BASE_DIR / \"exports\"\n\n# Supported export formats\nEXPORT_FORMATS = [\"pdf\", \"pptx\", \"docx\", \"txt\", \"json\", \"csv\", \"markdown\", \"html\"]\n\n# ============================================================================\n# LOGGING CONFIGURATION\n# ============================================================================\n\n# Log level: DEBUG | INFO | WARNING | ERROR | CRITICAL\nLOG_LEVEL = os.getenv(\"LOG_LEVEL\", \"INFO\")\n\n# Log message format\nLOG_FORMAT = (\n \"%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s\"\n)\n\n# Log file path\nLOG_FILE = BASE_DIR / \"logs\" / \"graphico.log\"\n\n# Log rotation: maximum file size before rotation (10 MB)\nLOG_MAX_BYTES = int(os.getenv(\"LOG_MAX_BYTES\", str(10 * 1024 * 1024)))\n\n# Log rotation: number of backup files to keep\nLOG_BACKUP_COUNT = int(os.getenv(\"LOG_BACKUP_COUNT\", \"5\"))\n\n# ============================================================================\n# RATE LIMITING\n# ============================================================================\n\n# Enable or disable rate limiting\nRATE_LIMIT_ENABLED = os.getenv(\"RATE_LIMIT_ENABLED\", \"True\").lower() == \"true\"\n\n# Number of requests allowed per time window\nRATE_LIMIT_REQUESTS = int(os.getenv(\"RATE_LIMIT_REQUESTS\", \"100\"))\n\n# Time window in seconds\nRATE_LIMIT_WINDOW_SECONDS = int(os.getenv(\"RATE_LIMIT_WINDOW_SECONDS\", \"60\"))\n\n# ============================================================================\n# CACHE CONFIGURATION\n# ============================================================================\n\n# Enable or disable caching\nCACHE_ENABLED = os.getenv(\"CACHE_ENABLED\", \"True\").lower() == \"true\"\n\n# Cache time-to-live in seconds (default: 5 minutes)\nCACHE_TTL_SECONDS = int(os.getenv(\"CACHE_TTL_SECONDS\", \"300\"))\n\n# Maximum number of cache entries\nCACHE_MAX_SIZE = int(os.getenv(\"CACHE_MAX_SIZE\", \"1000\"))\n\n# ============================================================================\n# SECURITY CONFIGURATION\n# ============================================================================\n\n# Bcrypt hashing rounds for password hashing (if needed in future)\nBCRYPT_ROUNDS = 12\n\n# Enable CSRF protection\nCSRF_ENABLED = os.getenv(\"CSRF_ENABLED\", \"True\").lower() == \"true\"\n\n# Maximum request body size (10 MB)\nMAX_REQUEST_SIZE_BYTES = int(os.getenv(\n \"MAX_REQUEST_SIZE_BYTES\",\n str(10 * 1024 * 1024)\n))\n\n# Request timeout in seconds\nREQUEST_TIMEOUT_SECONDS = int(os.getenv(\"REQUEST_TIMEOUT_SECONDS\", \"30\"))\n\n# ============================================================================\n# BUSINESS LOGIC CONSTANTS\n# ============================================================================\n\n# Maximum number of projects per user\nMAX_PROJECTS_PER_USER = int(os.getenv(\"MAX_PROJECTS_PER_USER\", \"50\"))\n\n# Maximum number of files per project\nMAX_FILES_PER_PROJECT = int(os.getenv(\"MAX_FILES_PER_PROJECT\", \"100\"))\n\n# Maximum chat history messages per project\nMAX_CHAT_HISTORY_LENGTH = int(os.getenv(\"MAX_CHAT_HISTORY_LENGTH\", \"500\"))\n\n# Maximum PDF file size for analysis in megabytes\nMAX_PDF_SIZE_MB = int(os.getenv(\"MAX_PDF_SIZE_MB\", \"25\"))\n\n# Maximum image generation resolution\nMAX_IMAGE_GENERATION_RESOLUTION = os.getenv(\n \"MAX_IMAGE_GENERATION_RESOLUTION\",\n \"2048x2048\"\n)\n\n# Maximum AI prompt length in characters\nMAX_PROMPT_LENGTH = int(os.getenv(\"MAX_PROMPT_LENGTH\", \"4000\"))\n\n# ============================================================================\n# DIRECTORY MANAGEMENT\n# ============================================================================\n\n# List of all directories that must exist for the application to function\nREQUIRED_DIRECTORIES = [\n DATABASE_PATH,\n UPLOAD_DIRECTORY,\n TEMP_DIRECTORY,\n EXPORT_DIRECTORY,\n BASE_DIR / \"logs\",\n BASE_DIR / \"models\",\n]\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef get_database_config() -> dict:\n \"\"\"\n Return the current database configuration as a dictionary.\n \n Returns:\n Dictionary with database type and connection details\n \"\"\"\n config = {\n \"type\": DATABASE_TYPE,\n \"path\": str(DATABASE_PATH),\n }\n \n if DATABASE_TYPE == \"json\":\n config[\"json\"] = {\n \"users_file\": str(JSON_USERS_FILE),\n \"projects_file\": str(JSON_PROJECTS_FILE),\n \"history_file\": str(JSON_HISTORY_FILE),\n \"sessions_file\": str(JSON_SESSIONS_FILE),\n \"oauth_states_file\": str(JSON_OAUTH_STATES_FILE),\n }\n elif DATABASE_TYPE == \"sqlite\":\n config[\"sqlite\"] = {\n \"path\": str(SQLITE_DATABASE_PATH),\n }\n elif DATABASE_TYPE == \"postgresql\":\n config[\"postgresql\"] = {\n \"host\": POSTGRES_HOST,\n \"port\": POSTGRES_PORT,\n \"database\": POSTGRES_DATABASE,\n \"user\": POSTGRES_USER,\n \"password_set\": bool(POSTGRES_PASSWORD),\n }\n \n return config\n\n\ndef get_ai_providers() -> dict:\n \"\"\"\n Return available AI providers and their configuration status.\n \n Returns:\n Dictionary mapping provider names to boolean (configured or not)\n \"\"\"\n return {\n \"github_models\": bool(GITHUB_MODELS_API_KEY),\n \"deepseek\": bool(DEEPSEEK_API_KEY),\n \"openai\": bool(OPENAI_API_KEY),\n \"anthropic\": bool(ANTHROPIC_API_KEY),\n \"google_ai\": bool(GOOGLE_AI_API_KEY),\n \"stability\": bool(STABILITY_API_KEY),\n \"replicate\": bool(REPLICATE_API_KEY),\n }\n\n\ndef get_configured_ai_providers() -> list:\n \"\"\"\n Return list of configured AI provider names.\n \n Returns:\n List of provider name strings that have API keys set\n \"\"\"\n providers = get_ai_providers()\n return [name for name, configured in providers.items() if configured]\n\n\ndef ensure_directories_exist() -> bool:\n \"\"\"\n Create all required directories if they don't exist.\n \n Returns:\n True if all directories exist or were created successfully,\n False if any directory creation failed\n \"\"\"\n all_ok = True\n \n for directory in REQUIRED_DIRECTORIES:\n try:\n directory.mkdir(parents=True, exist_ok=True)\n except OSError as e:\n print(f\"ERROR: Failed to create directory {directory}: {e}\")\n all_ok = False\n \n # Also ensure .gitkeep files exist to track empty directories\n for directory in REQUIRED_DIRECTORIES:\n gitkeep = directory / \".gitkeep\"\n if not gitkeep.exists():\n try:\n gitkeep.touch()\n except OSError:\n pass # Not critical\n \n return all_ok\n\n\ndef is_production() -> bool:\n \"\"\"\n Check if the server is running in production mode.\n \n Returns:\n True if SERVER_DEBUG is False (production), False otherwise\n \"\"\"\n return not SERVER_DEBUG\n\n\ndef validate_configuration() -> list:\n \"\"\"\n Validate the current configuration and return a list of issues.\n \n Checks for:\n - Missing or default secret keys (critical in production)\n - Missing API credentials\n - Directory existence\n - Configuration consistency\n \n Returns:\n List of warning/error message strings\n Messages prefixed with \"ERROR:\" indicate critical issues\n Messages prefixed with \"WARNING:\" indicate non-critical issues\n \"\"\"\n issues = []\n \n # Check for default secret keys (critical in production)\n default_secrets = {\n \"FLASK_SECRET_KEY\": \"graphico-pro-flask-secret-key-DEV-ONLY-change-in-production\",\n \"SESSION_SECRET_KEY\": \"graphico-pro-session-secret-DEV-ONLY-change-in-production\",\n \"OAUTH_STATE_SECRET\": \"graphico-oauth-state-secret-DEV-ONLY-change-in-production\",\n }\n \n for key_name, default_value in default_secrets.items():\n current_value = globals().get(key_name, \"\")\n if current_value == default_value:\n if is_production():\n issues.append(\n f\"ERROR: {key_name} is using the default value. \"\n f\"This is a critical security risk in production.\"\n )\n else:\n issues.append(\n f\"WARNING: {key_name} is using the default value. \"\n f\"Change this before deploying to production.\"\n )\n \n # Check Google OAuth configuration\n if not GOOGLE_CLIENT_SECRET:\n if is_production():\n issues.append(\n \"ERROR: GOOGLE_CLIENT_SECRET is not set. \"\n \"Google OAuth will not work.\"\n )\n else:\n issues.append(\n \"WARNING: GOOGLE_CLIENT_SECRET is not set. \"\n \"Google OAuth will not work until configured.\"\n )\n \n if not GOOGLE_CLIENT_ID:\n issues.append(\"ERROR: GOOGLE_CLIENT_ID is not configured.\")\n \n # Check AI provider configuration\n providers = get_ai_providers()\n configured_providers = [name for name, ok in providers.items() if ok]\n \n if not configured_providers:\n issues.append(\n \"WARNING: No AI providers are configured. \"\n \"AI features (chat, image generation, etc.) will not work. \"\n \"Set at least one API key.\"\n )\n \n # Check directory existence\n missing_dirs = []\n for directory in REQUIRED_DIRECTORIES:\n if not directory.exists():\n missing_dirs.append(str(directory))\n \n if missing_dirs:\n issues.append(\n f\"WARNING: Some directories are missing and will be created: \"\n f\"{', '.join(missing_dirs)}\"\n )\n \n # Check database configuration\n if DATABASE_TYPE == \"postgresql\" and not POSTGRES_PASSWORD:\n issues.append(\n \"WARNING: PostgreSQL is selected but POSTGRES_PASSWORD is not set.\"\n )\n \n # Check session security in production\n if is_production():\n if not SESSION_COOKIE_SECURE:\n issues.append(\n \"WARNING: SESSION_COOKIE_SECURE is False. \"\n \"Session cookies will be sent over HTTP. \"\n \"Enable this in production with HTTPS.\"\n )\n \n return issues\n\n\ndef get_config_summary() -> dict:\n \"\"\"\n Get a summary of the current configuration for display purposes.\n Sensitive values are masked.\n \n Returns:\n Dictionary with configuration summary\n \"\"\"\n return {\n \"server\": {\n \"host\": SERVER_HOST,\n \"port\": SERVER_PORT,\n \"debug\": SERVER_DEBUG,\n \"production\": is_production(),\n },\n \"database\": {\n \"type\": DATABASE_TYPE,\n \"path\": str(DATABASE_PATH),\n },\n \"ai_providers\": get_ai_providers(),\n \"oauth\": {\n \"client_id_configured\": bool(GOOGLE_CLIENT_ID),\n \"client_secret_configured\": bool(GOOGLE_CLIENT_SECRET),\n \"redirect_uris\": GOOGLE_REDIRECT_URIS,\n },\n \"session\": {\n \"expiry_hours\": SESSION_EXPIRY_HOURS,\n \"cookie_secure\": SESSION_COOKIE_SECURE,\n \"cookie_samesite\": SESSION_COOKIE_SAMESITE,\n },\n \"uploads\": {\n \"max_size_mb\": MAX_UPLOAD_SIZE_MB,\n \"allowed_extensions_count\": len(ALLOWED_UPLOAD_EXTENSIONS),\n },\n \"logging\": {\n \"level\": LOG_LEVEL,\n \"file\": str(LOG_FILE),\n },\n \"rate_limiting\": {\n \"enabled\": RATE_LIMIT_ENABLED,\n \"requests_per_window\": RATE_LIMIT_REQUESTS,\n \"window_seconds\": RATE_LIMIT_WINDOW_SECONDS,\n },\n }\n\n\ndef print_configuration_report() -> None:\n \"\"\"\n Print a formatted configuration report to the console.\n Useful for debugging and deployment verification.\n \"\"\"\n summary = get_config_summary()\n configured_providers = get_configured_ai_providers()\n \n print(\"\\n\" + \"=\" * 60)\n print(\"GRAPHICO PRO - CONFIGURATION REPORT\")\n print(\"=\" * 60)\n print(f\" Environment: {'PRODUCTION' if is_production() else 'DEVELOPMENT'}\")\n print(f\" Server: {summary['server']['host']}:{summary['server']['port']}\")\n print(f\" Database: {summary['database']['type']}\")\n print(f\" AI Providers: {len(configured_providers)} configured\")\n for provider in configured_providers:\n print(f\" - {provider}\")\n print(f\" OAuth Client ID: {'Configured' if summary['oauth']['client_id_configured'] else 'MISSING'}\")\n print(f\" OAuth Secret: {'Configured' if summary['oauth']['client_secret_configured'] else 'MISSING'}\")\n print(f\" Session Expiry: {summary['session']['expiry_hours']} hours\")\n print(f\" Max Upload: {summary['uploads']['max_size_mb']} MB\")\n print(f\" Log Level: {summary['logging']['level']}\")\n print(\"=\" * 60)\n \n issues = validate_configuration()\n if issues:\n print(\"\\nCONFIGURATION ISSUES:\")\n for issue in issues:\n print(f\" {issue}\")\n print(\"\")"}}}}