| 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 uuid |
| from concurrent.futures import ThreadPoolExecutor |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
| ) |
| logging.getLogger('apscheduler').setLevel(logging.WARNING) |
|
|
| |
| from backend.config import Config |
| from backend.utils.database import init_supabase |
| from backend.utils.cookies import setup_secure_cookies, configure_jwt_with_cookies |
|
|
| |
| from backend.scheduler.apscheduler_service import APSchedulerService |
|
|
| def setup_unicode_environment(): |
| """Setup Unicode environment for proper character handling.""" |
| try: |
| |
| os.environ['PYTHONIOENCODING'] = 'utf-8' |
| os.environ['PYTHONUTF8'] = '1' |
| |
| |
| 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 |
| |
| |
| if hasattr(sys.stdout, 'reconfigure'): |
| sys.stdout.reconfigure(encoding='utf-8', errors='replace') |
| sys.stderr.reconfigure(encoding='utf-8', errors='replace') |
| |
| |
| 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() |
| |
| app = Flask(__name__, static_folder='../frontend/dist') |
| app.config.from_object(Config) |
| |
| |
| app.url_map.strict_slashes = False |
| |
| |
| 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 |
| } |
| }) |
| |
| |
| @app.after_request |
| def add_cors_headers(response): |
| """Add CORS headers to all responses.""" |
| |
| origin = request.headers.get('Origin', '') |
| |
| |
| 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 |
| |
| |
| app = setup_secure_cookies(app) |
| |
| |
| jwt = configure_jwt_with_cookies(app) |
| |
| |
| app.supabase = init_supabase(app.config['SUPABASE_URL'], app.config['SUPABASE_KEY']) |
| |
| |
| |
| app.job_store = {} |
| |
| |
| |
| app.executor = ThreadPoolExecutor(max_workers=4) |
| |
| |
| 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") |
| |
| |
| 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()) |
| |
| |
| 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') |
| |
| |
| @app.route('/', defaults={'path': ''}) |
| @app.route('/<path:path>') |
| def serve_frontend(path): |
| |
| if path != "" and os.path.exists(os.path.join(app.static_folder, path)): |
| return send_from_directory(app.static_folder, path) |
| |
| elif path.startswith('api/'): |
| return {'error': 'Not found'}, 404 |
| |
| else: |
| return send_from_directory(app.static_folder, 'index.html') |
| |
| |
| @app.route('/health') |
| def health_check(): |
| return {'status': 'healthy', 'message': 'Lin backend is running'}, 200 |
| |
| |
| @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 |
| |
| |
| @app.route('/auth/callback') |
| def handle_auth_callback(): |
| """Handle OAuth callback from social networks.""" |
| try: |
| |
| 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}") |
| |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error={error}&from=linkedin" |
| return redirect(redirect_url) |
| |
| if not code or not state: |
| app.logger.error(f"π [OAuth] Missing required parameters") |
| |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=missing_params&from=linkedin" |
| return redirect(redirect_url) |
| |
| |
| token = request.cookies.get('access_token') |
| if not token: |
| app.logger.error(f"π [OAuth] No token found in cookies") |
| |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=no_token&from=linkedin" |
| return redirect(redirect_url) |
| |
| |
| try: |
| from flask_jwt_extended import decode_token |
| user_data = decode_token(token) |
| user_id = user_data['sub'] |
| app.logger.info(f"π [OAuth] Processing OAuth for user: {user_id}") |
| except Exception as jwt_error: |
| app.logger.error(f"π [OAuth] JWT verification failed: {str(jwt_error)}") |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=jwt_failed&from=linkedin" |
| return redirect(redirect_url) |
| |
| |
| from backend.services.linkedin_service import LinkedInService |
| linkedin_service = LinkedInService() |
| |
| |
| app.logger.info("π [OAuth] Exchanging code for access token...") |
| try: |
| token_response = linkedin_service.get_access_token(code) |
| access_token = token_response['access_token'] |
| app.logger.info(f"π [OAuth] Token exchange successful. Token length: {len(access_token)}") |
| except Exception as token_error: |
| app.logger.error(f"π [OAuth] Token exchange failed: {str(token_error)}") |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=token_exchange_failed&from=linkedin" |
| return redirect(redirect_url) |
| |
| |
| app.logger.info("π [OAuth] Fetching user info...") |
| try: |
| user_info = linkedin_service.get_user_info(access_token) |
| app.logger.info(f"π [OAuth] User info fetched: {user_info}") |
| except Exception as user_info_error: |
| app.logger.error(f"π [OAuth] User info fetch failed: {str(user_info_error)}") |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=user_info_failed&from=linkedin" |
| return redirect(redirect_url) |
| |
| |
| account_data = { |
| "social_network": "LinkedIn", |
| "account_name": user_info.get('name', 'LinkedIn Account'), |
| "id_utilisateur": user_id, |
| "token": access_token, |
| "sub": user_info.get('sub'), |
| "given_name": user_info.get('given_name'), |
| "family_name": user_info.get('family_name'), |
| "picture": user_info.get('picture') |
| } |
| app.logger.info(f"π [OAuth] Prepared account data: {account_data}") |
| |
| |
| app.logger.info("π [OAuth] Inserting account into database...") |
| try: |
| response = ( |
| app.supabase |
| .table("Social_network") |
| .insert(account_data) |
| .execute() |
| ) |
| |
| |
| app.logger.info(f"π [OAuth] Database response: {response}") |
| app.logger.info(f"π [OAuth] Response data: {response.data}") |
| app.logger.info(f"π [OAuth] Response error: {getattr(response, 'error', None)}") |
| |
| if response.data: |
| app.logger.info(f"π [OAuth] Account linked successfully for user: {user_id}") |
| |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?oauth_success=true&account_linked=true&from=linkedin" |
| return redirect(redirect_url) |
| else: |
| app.logger.error(f"π [OAuth] No data returned from database insertion for user: {user_id}") |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=database_insert_failed&from=linkedin" |
| return redirect(redirect_url) |
| |
| except Exception as db_error: |
| app.logger.error(f"π [OAuth] Database insertion failed: {str(db_error)}") |
| app.logger.error(f"π [OAuth] Database error type: {type(db_error)}") |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=database_error&from=linkedin" |
| return redirect(redirect_url) |
| |
| 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()}") |
| |
| from flask import redirect |
| redirect_url = f"{request.host_url.rstrip('/')}?error=server_error&from=linkedin" |
| return redirect(redirect_url) |
| |
| 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'] |
| ) |