import os from flask import Flask, jsonify, request from flask_cors import CORS from dotenv import load_dotenv import logging from logging.handlers import RotatingFileHandler import pymongo import datetime import secrets # Import Database early for logging connection from db import Database # Import routes from routes.department_routes import department_bp from routes.auth_routes import auth_bp from routes.workflow_routes import workflow_bp from routes.log_routes import log_bp from routes.incident_routes import incident_bp from routes.user_routes import user_bp # Load environment variables load_dotenv() # Create Flask app app = Flask(__name__) # Setup basic logging configuration first log_dir = '/tmp/logs' log_file = f"{log_dir}/app.log" try: if not os.path.exists(log_dir): os.makedirs(log_dir) handler = RotatingFileHandler(log_file, maxBytes=100000, backupCount=5) log_level = logging.INFO handler.setLevel(log_level) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) app.logger.addHandler(handler) # Also add console logging console_handler = logging.StreamHandler() console_handler.setLevel(log_level) console_handler.setFormatter(formatter) app.logger.addHandler(console_handler) app.logger.setLevel(log_level) app.logger.info('File logging configured successfully.') except Exception as e: # If file logging fails, ensure console logging is set up if not any(isinstance(h, logging.StreamHandler) for h in app.logger.handlers): console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') console_handler.setFormatter(formatter) app.logger.addHandler(console_handler) app.logger.setLevel(logging.INFO) app.logger.warning(f"Could not set up file logging to {log_file}: {str(e)}. Logging to console.") app.logger.info('Flask app created.') # Configure secrets with fallbacks for development app.config['SECRET_KEY'] = os.environ.get('JWT_SECRET') or secrets.token_hex(32) app.config['MONGO_URI'] = os.environ.get('MONGO_URI') app.logger.info('App secrets configured.') # Verify essential environment variables missing_vars = [] required_env_vars = ['MONGO_URI', 'OPENAI_API_KEY'] for var in required_env_vars: if not os.environ.get(var): missing_vars.append(var) if missing_vars: app.logger.warning(f"Missing essential environment variables: {', '.join(missing_vars)}") app.logger.warning("Please create a .env file or set Space secrets.") else: app.logger.info("All essential environment variables are present.") # Database Connection Test try: db_instance = Database.get_instance() db = db_instance.get_db() db.command('ping') app.logger.info("Successfully connected to MongoDB.") except Exception as e: app.logger.error(f"Failed to connect to MongoDB: {str(e)}") # Depending on severity, you might want to exit or prevent app run # Configure OpenAI API key if 'OPENAI_API_KEY' in os.environ: try: # We don't need to initialize the client globally # It will be initialized in each function that needs it app.logger.info("OpenAI API key available.") except Exception as e: app.logger.error(f"Failed to import OpenAI: {str(e)}") else: app.logger.warning("OPENAI_API_KEY not found in environment variables.") # Enable CORS with specific origins origins = [ "http://localhost:3000", # Development frontend "https://www.tryenflow.com", # Production frontend "https://droov-enflow-api.hf.space" # Add the Hugging Face Space URL ] try: CORS(app, resources={r"/api/*": {"origins": origins}}, supports_credentials=True) app.logger.info(f"CORS configured for origins: {origins}") except Exception as e: app.logger.error(f"Failed to configure CORS: {str(e)}") # Register blueprints try: app.register_blueprint(auth_bp, url_prefix='/api/auth') app.register_blueprint(department_bp, url_prefix='/api/departments') app.register_blueprint(workflow_bp, url_prefix='/api/workflows') app.register_blueprint(log_bp, url_prefix='/api/logs') app.register_blueprint(incident_bp, url_prefix='/api/incidents') app.register_blueprint(user_bp, url_prefix='/api/users') app.logger.info("All API blueprints registered successfully.") except Exception as e: app.logger.error(f"Failed to register blueprints: {str(e)}") # Set up uploads directory in /tmp (which should be writable) app.config['UPLOAD_FOLDER'] = '/tmp/uploads' if not os.path.exists(app.config['UPLOAD_FOLDER']): try: os.makedirs(app.config['UPLOAD_FOLDER']) app.logger.info(f"Uploads directory created at {app.config['UPLOAD_FOLDER']}") except PermissionError: app.logger.warning(f"Permission denied creating {app.config['UPLOAD_FOLDER']}. Using /tmp fallback.") app.config['UPLOAD_FOLDER'] = '/tmp' except Exception as e: app.logger.error(f"Error creating uploads directory {app.config['UPLOAD_FOLDER']}: {str(e)}") # Error handler @app.errorhandler(Exception) def handle_exception(e): # Log the actual exception trace app.logger.exception(f"Unhandled exception: {str(e)}") return jsonify({"error": "An unexpected internal error occurred"}), 500 # Root route @app.route('/') def index(): app.logger.debug("Root route / accessed") return jsonify({ "message": "Enflow API is running", "version": "1.0.0" }) # Test route that doesn't require authentication @app.route('/api/test') def test_route(): app.logger.info("/api/test route accessed") return jsonify({ "message": "API test successful", "timestamp": datetime.datetime.now().isoformat(), "environment": os.environ.get('FLASK_DEBUG', '0') # Use FLASK_DEBUG now }) # Health check route @app.route('/health') def health_check(): app.logger.info("Health check requested") db_status = "unknown" env_status = "ok" error_details = "" status_code = 200 try: # Check MongoDB connection db = Database.get_instance().get_db() db.command('ping') db_status = "connected" app.logger.info("Health Check: MongoDB connection successful.") except Exception as e: db_status = "disconnected" error_details += f"MongoDB Error: {str(e)}; " app.logger.error(f"Health Check: MongoDB connection failed: {str(e)}") status_code = 503 # Service Unavailable # Check environment variables if missing_vars: env_status = f"Missing: {', '.join(missing_vars)}" if status_code == 200: # Don't downgrade status if DB already failed status_code = 500 # Internal Server Error for config issue error_details += f"Missing environment variables: {', '.join(missing_vars)}; " app.logger.warning(f"Health Check: Missing environment variables: {', '.join(missing_vars)}") response = { "status": "healthy" if status_code == 200 else "unhealthy", "mongo": db_status, "env_vars": env_status, "upload_dir_exists": os.path.exists(app.config.get('UPLOAD_FOLDER', '/tmp')), "log_dir_exists": os.path.exists(log_dir) } if error_details: response["errors"] = error_details.strip() return jsonify(response), status_code # Logging setup moved to the top if __name__ == '__main__': # Use FLASK_DEBUG instead of FLASK_ENV for debug mode control debug_mode = os.environ.get('FLASK_DEBUG', '0') == '1' # Port configuration - Hugging Face might override this via its execution environment port = int(os.environ.get('PORT', 7860)) # Default to 7860 if PORT env var not set app.logger.info(f"Starting Flask app. Debug mode: {debug_mode}. Port: {port}") # Note: When running via gunicorn (common in HF), gunicorn command sets the port, # and this app.run() is not executed directly. # The port setting here is mainly for local `python app.py` execution. app.run(host='0.0.0.0', port=port, debug=debug_mode)