""" DigiServer v2 - Application Factory Modern Flask application with blueprint architecture """ import os from flask import Flask, render_template from dotenv import load_dotenv from app.config import DevelopmentConfig, ProductionConfig, TestingConfig from app.extensions import db, bcrypt, login_manager, migrate, cache # Load environment variables load_dotenv() def create_app(config_name=None): """ Application factory pattern Args: config_name: Configuration environment (development, production, testing) Returns: Flask application instance """ # Set instance path to absolute path instance_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'instance')) app = Flask(__name__, instance_path=instance_path, instance_relative_config=True) # Load configuration if config_name == 'production': config = ProductionConfig elif config_name == 'testing': config = TestingConfig else: config = DevelopmentConfig app.config.from_object(config) # Initialize extensions db.init_app(app) bcrypt.init_app(app) login_manager.init_app(app) migrate.init_app(app, db) cache.init_app(app) # Configure Flask-Login configure_login_manager(app) # Register components register_blueprints(app) register_error_handlers(app) register_commands(app) register_context_processors(app) register_template_filters(app) return app def register_blueprints(app): """Register application blueprints""" from app.blueprints.main import main_bp from app.blueprints.auth import auth_bp from app.blueprints.admin import admin_bp from app.blueprints.players import players_bp from app.blueprints.groups import groups_bp from app.blueprints.content import content_bp from app.blueprints.playlist import playlist_bp from app.blueprints.api import api_bp # Register blueprints (using URL prefixes from blueprint definitions) app.register_blueprint(main_bp) app.register_blueprint(auth_bp) app.register_blueprint(admin_bp) app.register_blueprint(players_bp) app.register_blueprint(groups_bp) app.register_blueprint(content_bp) app.register_blueprint(playlist_bp) app.register_blueprint(api_bp) def configure_login_manager(app): """Configure Flask-Login""" from app.models.user import User login_manager.login_view = 'auth.login' login_manager.login_message = 'Please log in to access this page.' login_manager.login_message_category = 'info' @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) def register_error_handlers(app): """Register error handlers""" @app.errorhandler(404) def not_found(error): return render_template('errors/404.html'), 404 @app.errorhandler(403) def forbidden(error): return render_template('errors/403.html'), 403 @app.errorhandler(500) def internal_error(error): db.session.rollback() return render_template('errors/500.html'), 500 @app.errorhandler(413) def request_entity_too_large(error): return render_template('errors/413.html'), 413 @app.errorhandler(408) def request_timeout(error): return render_template('errors/408.html'), 408 def register_commands(app): """Register CLI commands""" import click @app.cli.command('init-db') def init_db(): """Initialize the database""" db.create_all() click.echo('Database initialized.') @app.cli.command('create-admin') @click.option('--username', default='admin', help='Admin username') @click.option('--password', prompt=True, hide_input=True, help='Admin password') def create_admin(username, password): """Create an admin user""" from app.models.user import User # Check if user exists if User.query.filter_by(username=username).first(): click.echo(f'User {username} already exists.') return # Create admin user hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') admin = User(username=username, password=hashed_password, role='admin') db.session.add(admin) db.session.commit() click.echo(f'Admin user {username} created successfully.') @app.cli.command('seed-db') def seed_db(): """Seed database with sample data (development only)""" if app.config['ENV'] == 'production': click.echo('Cannot seed database in production.') return click.echo('Seeding database with sample data...') # Add your seeding logic here click.echo('Database seeded successfully.') def register_context_processors(app): """Register context processors for templates""" from flask_login import current_user @app.context_processor def inject_config(): """Inject configuration variables into all templates""" return { 'server_version': app.config['SERVER_VERSION'], 'build_date': app.config['BUILD_DATE'], 'logo_exists': os.path.exists( os.path.join(app.root_path, app.config['UPLOAD_FOLDERLOGO'], 'logo.png') ) } @app.context_processor def inject_user_theme(): """Inject user theme preference""" theme = 'light' if current_user.is_authenticated and hasattr(current_user, 'theme'): theme = current_user.theme return {'theme': theme} def register_template_filters(app): """Register custom Jinja2 template filters""" from datetime import datetime, timezone @app.template_filter('localtime') def localtime_filter(dt, format='%Y-%m-%d %H:%M'): """Convert UTC datetime to local time and format it. Args: dt: datetime object in UTC format: strftime format string Returns: Formatted datetime string in local timezone """ if dt is None: return '' # If datetime is naive (no timezone), assume it's UTC if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) # Convert to local time local_dt = dt.astimezone() return local_dt.strftime(format) # For backwards compatibility and direct running if __name__ == '__main__': app = create_app() app.run(host='0.0.0.0', port=5000, debug=True)