123 lines
5.0 KiB
Python
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 |