# backend/__init__.py import os import logging import psycopg2.errors from flask import Flask from werkzeug.middleware.proxy_fix import ProxyFix logger = logging.getLogger(__name__) def ensure_owner_exists(): """ Ensure that an application owner exists. This is a backup mechanism in case the migration failed. Runs automatically on app startup. """ from .db_handler import get_db_connection, release_db_connection conn = None try: conn = get_db_connection() with conn.cursor() as cur: # First check if the is_owner column exists (handles migration timing issue) try: cur.execute(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'is_owner' """) column_exists = cur.fetchone() is not None if not column_exists: logger.info("is_owner column not yet created, skipping owner check (migrations may still be running)") return # Check if any owner exists cur.execute("SELECT COUNT(*) FROM users WHERE is_owner = TRUE") owner_count = cur.fetchone()[0] except psycopg2.errors.UndefinedColumn: logger.info("is_owner column not yet created, skipping owner check (migrations may still be running)") return if owner_count == 0: logger.info("No application owner found, attempting to promote first user...") # Find the first user using multiple strategies first_user_id = None # Strategy 1: First user by created_at try: cur.execute(""" SELECT id FROM users WHERE created_at IS NOT NULL ORDER BY created_at ASC, id ASC LIMIT 1 """) result = cur.fetchone() if result: first_user_id = result[0] except Exception as e: logger.warning(f"Strategy 1 (created_at) failed: {e}") # Strategy 2: First user by ID if Strategy 1 failed if first_user_id is None: try: cur.execute("SELECT id FROM users ORDER BY id ASC LIMIT 1") result = cur.fetchone() if result: first_user_id = result[0] except Exception as e: logger.warning(f"Strategy 2 (lowest ID) failed: {e}") # Strategy 3: First admin user if other strategies failed if first_user_id is None: try: cur.execute("SELECT id FROM users WHERE is_admin = TRUE ORDER BY id ASC LIMIT 1") result = cur.fetchone() if result: first_user_id = result[0] except Exception as e: logger.warning(f"Strategy 3 (first admin) failed: {e}") # Promote the user to owner if found if first_user_id is not None: try: cur.execute("UPDATE users SET is_owner = TRUE WHERE id = %s", (first_user_id,)) conn.commit() logger.info(f"✅ Automatically promoted user ID {first_user_id} to application owner") # Log user info for confirmation cur.execute("SELECT username, email FROM users WHERE id = %s", (first_user_id,)) user_info = cur.fetchone() if user_info: logger.info(f"Owner is now: {user_info[0]} ({user_info[1]})") except Exception as e: logger.error(f"Failed to promote user {first_user_id} to owner: {e}") conn.rollback() else: logger.warning("No users found in system. First registered user will become owner.") else: # Log current owner for confirmation cur.execute("SELECT id, username, email FROM users WHERE is_owner = TRUE") owners = cur.fetchall() logger.info(f"Application owner(s) confirmed: {[f'{o[1]} (ID:{o[0]})' for o in owners]}") except Exception as e: logger.error(f"Error in ensure_owner_exists: {e}") if conn: conn.rollback() finally: if conn: release_db_connection(conn) def create_app(config_name=None): """Create and configure an instance of the Flask application.""" # Configure logging FIRST logging.basicConfig(level=logging.INFO) logger.info("Starting application factory...") # Determine configuration if config_name is None: config_name = os.environ.get('FLASK_CONFIG', 'default') # Create Flask app instance app = Flask(__name__) # Apply ProxyFix middleware for reverse proxy support app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) # Load configuration try: from .config import config app.config.from_object(config[config_name]) config[config_name].init_app(app) logger.info(f"Configuration '{config_name}' loaded successfully") except ImportError: try: from config import config app.config.from_object(config[config_name]) config[config_name].init_app(app) logger.info(f"Configuration '{config_name}' loaded successfully") except Exception as e: logger.error(f"Failed to load configuration: {e}") raise # Initialize extensions try: from .extensions import initialize_extensions initialize_extensions(app) except ImportError: from extensions import initialize_extensions initialize_extensions(app) # Register Blueprints within app context with app.app_context(): try: # Import blueprints from .auth_routes import auth_bp from .oidc_handler import oidc_bp from .warranties_routes import warranties_bp from .admin_routes import admin_bp from .statistics_routes import statistics_bp from .tags_routes import tags_bp from .file_routes import file_bp except ImportError: # Fallback for development from auth_routes import auth_bp from oidc_handler import oidc_bp from warranties_routes import warranties_bp from admin_routes import admin_bp from statistics_routes import statistics_bp from tags_routes import tags_bp from file_routes import file_bp # Register blueprints app.register_blueprint(auth_bp, url_prefix='/api/auth') app.register_blueprint(oidc_bp, url_prefix='/api') app.register_blueprint(warranties_bp, url_prefix='/api') app.register_blueprint(admin_bp, url_prefix='/api/admin') app.register_blueprint(statistics_bp, url_prefix='/api') app.register_blueprint(tags_bp, url_prefix='/api') app.register_blueprint(file_bp, url_prefix='/api') logger.info("All blueprints registered successfully") # Initialize OIDC client after extensions and blueprints try: from .db_handler import get_db_connection, release_db_connection from .oidc_handler import init_oidc_client init_oidc_client(app, get_db_connection, release_db_connection) except ImportError: from db_handler import get_db_connection, release_db_connection from oidc_handler import init_oidc_client init_oidc_client(app, get_db_connection, release_db_connection) # Ensure an owner exists on startup ensure_owner_exists() # Initialize the notification scheduler with the app context try: from .notifications import init_scheduler from .db_handler import get_db_connection, release_db_connection init_scheduler(app, get_db_connection, release_db_connection) logger.info("✅ Notification scheduler initialized successfully in factory") except ImportError: try: from notifications import init_scheduler from db_handler import get_db_connection, release_db_connection init_scheduler(app, get_db_connection, release_db_connection) logger.info("✅ Notification scheduler initialized successfully in factory (dev mode)") except Exception as e: logger.error(f"❌ Failed to initialize notification scheduler: {e}") # Continue without notifications - the app can still function except Exception as e: logger.error(f"❌ Failed to initialize notification scheduler: {e}") # Continue without notifications - the app can still function logger.info("Application factory completed successfully") return app