import os import sys import locale import logging from flask import Flask, send_from_directory, request from flask_cors import CORS from flask_jwt_extended import JWTManager # Import for job handling import uuid from concurrent.futures import ThreadPoolExecutor # Configure logging for APScheduler - only show essential logs logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logging.getLogger('apscheduler').setLevel(logging.WARNING) # Use relative import for the Config class to work with Hugging Face Spaces from backend.config import Config from backend.utils.database import init_supabase from backend.utils.cookies import setup_secure_cookies, configure_jwt_with_cookies # APScheduler imports from backend.scheduler.apscheduler_service import APSchedulerService def setup_unicode_environment(): """Setup Unicode environment for proper character handling.""" try: # Set environment variables for UTF-8 support os.environ['PYTHONIOENCODING'] = 'utf-8' os.environ['PYTHONUTF8'] = '1' # Set locale to UTF-8 if available try: locale.setlocale(locale.LC_ALL, 'C.UTF-8') except locale.Error: try: locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') except locale.Error: try: locale.setlocale(locale.LC_ALL, '') except locale.Error: pass # Set stdout/stderr encoding to UTF-8 if possible if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8', errors='replace') sys.stderr.reconfigure(encoding='utf-8', errors='replace') # Log to app logger instead of print if 'app' in globals(): app.logger.info("Unicode environment setup completed") except Exception as e: if 'app' in globals(): app.logger.warning(f"Unicode setup failed: {str(e)}") def create_app(): """Create and configure the Flask application.""" # Setup Unicode environment first setup_unicode_environment() app = Flask(__name__, static_folder='../frontend/dist') app.config.from_object(Config) # Disable strict slashes to prevent redirects app.url_map.strict_slashes = False # Initialize CORS with specific configuration CORS(app, resources={ r"/api/*": { "origins": [ "http://localhost:3000", "http://localhost:5000", "http://127.0.0.1:3000", "http://127.0.0.1:5000", "http://192.168.1.4:3000", "https://zelyanoth-lin-cbfcff2.hf.space" ], "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"], "allow_headers": ["Content-Type", "Authorization", "X-Requested-With"], "supports_credentials": True, "max_age": 86400 # 24 hours } }) # Add CORS headers for all routes @app.after_request def add_cors_headers(response): """Add CORS headers to all responses.""" # Get the origin from the request origin = request.headers.get('Origin', '') # Check if the origin is in our allowed list allowed_origins = [ "http://localhost:3000", "http://localhost:5000", "http://127.0.0.1:3000", "http://127.0.0.1:5000", "http://192.168.1.4:3000", "https://zelyanoth-lin-cbfcff2.hf.space" ] if origin in allowed_origins: response.headers.add('Access-Control-Allow-Origin', origin) response.headers.add('Access-Control-Allow-Credentials', 'true') response.headers.add('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With') return response # Setup secure cookies app = setup_secure_cookies(app) # Initialize JWT with cookie support jwt = configure_jwt_with_cookies(app) # Initialize Supabase client app.supabase = init_supabase(app.config['SUPABASE_URL'], app.config['SUPABASE_KEY']) # Initialize a simple in-memory job store for tracking async tasks # In production, you'd use a database or Redis for this app.job_store = {} # Initialize a ThreadPoolExecutor for running background tasks # In production, you'd use a proper task scheduler like APScheduler app.executor = ThreadPoolExecutor(max_workers=4) # Initialize APScheduler if app.config.get('SCHEDULER_ENABLED', True): try: from backend.scheduler.apscheduler_service import APSchedulerService scheduler = APSchedulerService(app) app.scheduler = scheduler app.logger.info("APScheduler initialized successfully") # Verify APScheduler initialization if hasattr(app, 'scheduler') and app.scheduler.scheduler is not None: app.logger.info("✅ APScheduler initialized successfully") app.logger.info(f"📊 Current jobs: {len(app.scheduler.scheduler.get_jobs())}") app.logger.info("🔄 Schedule loading job added (runs every 5 minutes)") else: app.logger.warning("⚠️ APScheduler initialization failed") except Exception as e: app.logger.error(f"Failed to initialize APScheduler: {str(e)}") import traceback app.logger.error(traceback.format_exc()) # Register blueprints from backend.api.auth import auth_bp from backend.api.sources import sources_bp from backend.api.accounts import accounts_bp from backend.api.posts import posts_bp from backend.api.schedules import schedules_bp app.register_blueprint(auth_bp, url_prefix='/api/auth') app.register_blueprint(sources_bp, url_prefix='/api/sources') app.register_blueprint(accounts_bp, url_prefix='/api/accounts') app.register_blueprint(posts_bp, url_prefix='/api/posts') app.register_blueprint(schedules_bp, url_prefix='/api/schedules') # Serve frontend static files @app.route('/', defaults={'path': ''}) @app.route('/') def serve_frontend(path): # If the path is a file (has a dot), try to serve it from dist if path != "" and os.path.exists(os.path.join(app.static_folder, path)): return send_from_directory(app.static_folder, path) # For API routes, return 404 elif path.startswith('api/'): return {'error': 'Not found'}, 404 # Otherwise, serve index.html for SPA routing else: return send_from_directory(app.static_folder, 'index.html') # Health check endpoint @app.route('/health') def health_check(): return {'status': 'healthy', 'message': 'Lin backend is running'}, 200 # Add database connection check endpoint @app.route('/api/health') def api_health_check(): """Enhanced health check that includes database connection.""" try: from backend.utils.database import check_database_connection db_connected = check_database_connection(app.supabase) return { 'status': 'healthy' if db_connected else 'degraded', 'database': 'connected' if db_connected else 'disconnected', 'message': 'Lin backend is running' if db_connected else 'Database connection issues' }, 200 if db_connected else 503 except Exception as e: return { 'status': 'unhealthy', 'database': 'error', 'message': f'Health check failed: {str(e)}' }, 503 # Add OAuth callback handler route @app.route('/auth/callback') def handle_auth_callback(): """Handle OAuth callback from social networks.""" try: # Parse URL parameters import requests from urllib.parse import parse_qs, urlparse url = request.url parsed_url = urlparse(url) query_params = parse_qs(parsed_url.query) code = query_params.get('code', [None])[0] state = query_params.get('state', [None])[0] error = query_params.get('error', [None])[0] app.logger.info(f"🔗 [OAuth] Direct callback handler triggered") app.logger.info(f"🔗 [OAuth] URL: {url}") app.logger.info(f"🔗 [OAuth] Code: {code[:20] + '...' if code else None}") app.logger.info(f"🔗 [OAuth] State: {state}") app.logger.info(f"🔗 [OAuth] Error: {error}") if error: app.logger.error(f"🔗 [OAuth] OAuth error: {error}") # Return error page or redirect with error return f""" Authentication Error

Authentication Failed

Error: {error}

Go back to sources

""", 400 if not code or not state: app.logger.error(f"🔗 [OAuth] Missing required parameters") return """ Invalid Callback

Invalid Callback

Missing required parameters.

Go back to sources

""", 400 # Forward the callback to the accounts API app.logger.info(f"🔗 [OAuth] Forwarding callback to accounts API") # Get the JWT token from cookies from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token import jwt # Try to get token from cookies token = request.cookies.get('access_token') if not token: app.logger.error(f"🔗 [OAuth] No token found in cookies") return """ Authentication Error

Authentication Error

No authentication token found.

Please login again

""", 401 # Make the callback request to the accounts API api_url = f"{request.host_url.rstrip('/')}/api/accounts/callback" headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {token}' } callback_data = { 'code': code, 'state': state, 'social_network': 'LinkedIn' } app.logger.info(f"🔗 [OAuth] Making POST request to {api_url}") response = requests.post(api_url, json=callback_data, headers=headers, timeout=30) app.logger.info(f"🔗 [OAuth] API response status: {response.status_code}") app.logger.info(f"🔗 [OAuth] API response data: {response.text}") if response.status_code == 200: result = response.json() if result.get('success'): app.logger.info(f"🔗 [OAuth] Account linked successfully") # Return success page return f""" Authentication Successful

Authentication Successful!

Your LinkedIn account has been linked successfully.

Go to your sources

""" else: app.logger.error(f"🔗 [OAuth] Account linking failed: {result.get('message')}") return f""" Authentication Failed

Authentication Failed

{result.get('message', 'Unknown error')}

Go back to sources

""", 400 else: app.logger.error(f"🔗 [OAuth] API request failed: {response.status_code}") return f""" Authentication Error

Authentication Error

Failed to complete authentication: {response.status_code}

Go back to sources

""", 500 except Exception as e: app.logger.error(f"🔗 [OAuth] Callback handler error: {str(e)}") import traceback app.logger.error(f"🔗 [OAuth] Traceback: {traceback.format_exc()}") return f""" Authentication Error

Authentication Error

An error occurred during authentication: {str(e)}

Go back to sources

""", 500 return app if __name__ == '__main__': app = create_app() app.run( host='0.0.0.0', port=int(os.environ.get('PORT', 5000)), debug=app.config['DEBUG'] )