import os from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager from flask_bcrypt import Bcrypt from app.config import config # Initialize extensions db = SQLAlchemy() migrate = Migrate() login_manager = LoginManager() bcrypt = Bcrypt() def create_app(config_name='default'): """Application factory pattern.""" app = Flask(__name__) # Load configuration app.config.from_object(config[config_name]) # Initialize extensions db.init_app(app) migrate.init_app(app, db) bcrypt.init_app(app) # Configure Flask-Login login_manager.init_app(app) login_manager.login_view = 'auth.login' login_manager.login_message = 'Please log in to access this page.' login_manager.login_message_category = 'info' # User loader for Flask-Login from app.models.user import User @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # Register blueprints from app.routes.auth import auth_bp from app.routes.main import main_bp from app.routes.sets import sets_bp from app.routes.instructions import instructions_bp from app.routes.admin import admin_bp from app.routes.extra_files import extra_files_bp from app.routes.moc import bp as moc_bp from app.routes.backup import backup_bp app.register_blueprint(auth_bp) app.register_blueprint(main_bp) app.register_blueprint(sets_bp) app.register_blueprint(instructions_bp) app.register_blueprint(admin_bp) app.register_blueprint(extra_files_bp) app.register_blueprint(moc_bp) app.register_blueprint(backup_bp) # Import models to ensure they're registered with SQLAlchemy from app.models.user import User from app.models.set import Set from app.models.instruction import Instruction from app.models.extra_file import ExtraFile # Create upload directories os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'pdfs'), exist_ok=True) os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'images'), exist_ok=True) os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'covers'), exist_ok=True) os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'thumbnails'), exist_ok=True) os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'extra_files'), exist_ok=True) # Create backup directory os.makedirs(app.config['BACKUP_FOLDER'], exist_ok=True) # Serve uploads from UPLOAD_FOLDER wherever it lives. # Stored paths are rendered as /static/uploads/, which only works # by accident when UPLOAD_FOLDER sits inside app/static. This explicit # route takes precedence over Flask's static handler and keeps those # URLs valid when the folder is a mounted volume (e.g. /data/uploads). from flask import send_from_directory @app.route('/static/uploads/') def uploaded_file(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename) # Register CLI commands (flask backup create/list/verify/restore) from app.cli import register_cli register_cli(app) # Context processor for global template variables @app.context_processor def inject_global_vars(): from app.services.brickset_api import BricksetAPI from datetime import datetime return { 'app_name': 'LEGO Instructions Manager', 'brickset_available': BricksetAPI.is_configured(), 'current_year': datetime.now().year } return app