mirror of
https://github.com/sassanix/Warracker.git
synced 2025-12-31 18:49:39 -06:00
In 60239bd637 the assumption that init_oidc_client could be imported
from oidc_handler was introduced into admin_routes.py.
However, init_oidc_client was defined in __init__.py, leaving us with
the options of:
1. Importing from .
2. Moving the implementation to oidc_handler, and importing in
__init__.py
I've gone with option 2.
220 lines
9.4 KiB
Python
220 lines
9.4 KiB
Python
# 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
|