import os import re # For path parsing from flask import Flask from flask_jwt_extended import JWTManager from flask_cors import CORS from dotenv import load_dotenv # Import database components from .database import db # Assuming db = SQLAlchemy() is in database.py # Import blueprints from .auth import auth_bp from .milk import milk_bp from .consumption import consumption_bp from .overview import overview_bp from .profile import profile_bp from .devices import device_bp from .main_routes import main_bp # Import main routes load_dotenv() # Load environment variables from .env file def create_app(database_uri_override=None): # Allow overriding URI for testing etc. # instance_relative_config=True tells Flask instance folder is relative to app root app = Flask(__name__, instance_relative_config=True) # --- Ensure instance folder exists --- # Flask uses app.instance_path for this. os.makedirs won't hurt if it exists. try: # app.instance_path is the absolute path to the instance folder os.makedirs(app.instance_path, exist_ok=True) print(f"Checked/Created instance folder: {app.instance_path}") except OSError as e: print(f"CRITICAL: Could not create instance folder at {app.instance_path}: {e}") # Depending on the error, you might want to exit or raise raise # Re-raise the error as this is likely fatal # --- Configurations --- # Load default config, then potentially override from .env or function arg app.config.from_mapping( SECRET_KEY=os.environ.get('FLASK_SECRET_KEY', 'default-flask-secret-key-change-me'), JWT_SECRET_KEY=os.environ.get('JWT_SECRET_KEY', 'default-jwt-secret-key-change-me'), # Default to using the instance folder, common Flask practice SQLALCHEMY_DATABASE_URI=f"sqlite:///{os.path.join(app.instance_path, 'milk_tracker.db')}", SQLALCHEMY_TRACK_MODIFICATIONS=False, JSON_SORT_KEYS=False # Keep JSON order as defined in dicts ) # Override DB URI if specified in environment or function call env_db_uri = os.environ.get('DATABASE_URL') final_db_uri = database_uri_override or env_db_uri or app.config['SQLALCHEMY_DATABASE_URI'] app.config['SQLALCHEMY_DATABASE_URI'] = final_db_uri print(f"Using Database URI: {app.config['SQLALCHEMY_DATABASE_URI']}") # --- Ensure Database DIRECTORY Exists BEFORE db.init_app --- db_uri = app.config['SQLALCHEMY_DATABASE_URI'] if db_uri.startswith('sqlite:///'): # Extract path part after 'sqlite:///' db_path_part = db_uri[len('sqlite:///'):] db_abs_path = None # Initialize # Handle Windows drive letters (e.g., C:/...) if present after /// # Use os.path.splitdrive for robustness drive, path_no_drive = os.path.splitdrive(db_path_part) if drive: db_abs_path = os.path.abspath(db_path_part) # It's an absolute Windows path # Handle absolute Unix paths (starting with /) elif os.path.isabs(db_path_part): db_abs_path = db_path_part # Otherwise, treat as relative path (build from instance path) else: db_abs_path = os.path.join(app.instance_path, db_path_part) # Get the directory containing the potentially non-existent db file db_dir = os.path.dirname(db_abs_path) # Create the directory if it doesn't exist if db_dir: # Check if db_dir is not empty (i.e., not just a filename) try: os.makedirs(db_dir, exist_ok=True) print(f"Checked/Created database directory: {db_dir}") except OSError as e: print(f"CRITICAL: Could not create database directory {db_dir}: {e}") raise # Re-raise as this is likely fatal # else: print("Database file is in the root or instance folder, no sub-directory needed.") # --- Initialize Extensions --- # Now that the directory should exist, this should succeed try: db.init_app(app) print("SQLAlchemy initialized.") except Exception as e: print(f"CRITICAL: Failed to initialize SQLAlchemy: {e}") raise # Re-raise JWTManager(app) CORS(app) # Allow requests from your frontend domain in production # --- Create Database Tables --- # This ensures tables are created *after* successful initialization try: with app.app_context(): db.create_all() print(f"Database tables checked/created.") except Exception as e: print(f"CRITICAL: Failed to create database tables: {e}") raise # Re-raise # --- Register Blueprints --- app.register_blueprint(auth_bp) app.register_blueprint(milk_bp) app.register_blueprint(consumption_bp) app.register_blueprint(overview_bp) app.register_blueprint(profile_bp) app.register_blueprint(device_bp) app.register_blueprint(main_bp) # Register the main route for serving HTML @app.route('/health') # Add a simple health check endpoint def health(): return "API OK", 200 return app