2025-04-09 20:34:32 +08:00

123 lines
5.0 KiB
Python

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