"""Flask application factory.""" import os import logging from flask import Flask, send_from_directory from .config import config from .extensions import db, migrate, jwt, cors, ma, init_extensions from .plugins import plugin_manager def create_app(config_name: str = None) -> Flask: """ Application factory. Args: config_name: Configuration name ('development', 'production', 'testing') Returns: Configured Flask application """ if config_name is None: config_name = os.environ.get('FLASK_ENV', 'development') app = Flask(__name__, instance_relative_config=True) # Load configuration app.config.from_object(config.get(config_name, config['default'])) # Load instance config if exists app.config.from_pyfile('config.py', silent=True) # Ensure instance folder exists os.makedirs(app.instance_path, exist_ok=True) # Configure logging configure_logging(app) # Initialize extensions init_extensions(app) # Initialize plugin manager with app.app_context(): plugin_manager.init_app(app, db) # Register core blueprints register_blueprints(app) # Register CLI commands register_cli_commands(app) # Register error handlers register_error_handlers(app) # Serve Vue frontend register_frontend_routes(app) # JWT user loader (identity is a string in JWT, convert to int for DB lookup) @jwt.user_lookup_loader def user_lookup_callback(_jwt_header, jwt_data): from .core.models import User identity = jwt_data["sub"] return User.query.get(int(identity)) return app def register_blueprints(app: Flask): """Register core API blueprints.""" from .core.api import ( auth_bp, assets_bp, machines_bp, machinetypes_bp, pctypes_bp, statuses_bp, vendors_bp, models_bp, businessunits_bp, locations_bp, operatingsystems_bp, dashboard_bp, applications_bp, knowledgebase_bp, search_bp, reports_bp, collector_bp, employees_bp, slides_bp, settings_bp, auditlogs_bp, users_bp, ) api_prefix = '/api' app.register_blueprint(auth_bp, url_prefix=f'{api_prefix}/auth') app.register_blueprint(assets_bp, url_prefix=f'{api_prefix}/assets') app.register_blueprint(machines_bp, url_prefix=f'{api_prefix}/machines') app.register_blueprint(machinetypes_bp, url_prefix=f'{api_prefix}/machinetypes') app.register_blueprint(pctypes_bp, url_prefix=f'{api_prefix}/pctypes') app.register_blueprint(statuses_bp, url_prefix=f'{api_prefix}/statuses') app.register_blueprint(vendors_bp, url_prefix=f'{api_prefix}/vendors') app.register_blueprint(models_bp, url_prefix=f'{api_prefix}/models') app.register_blueprint(businessunits_bp, url_prefix=f'{api_prefix}/businessunits') app.register_blueprint(locations_bp, url_prefix=f'{api_prefix}/locations') app.register_blueprint(operatingsystems_bp, url_prefix=f'{api_prefix}/operatingsystems') app.register_blueprint(dashboard_bp, url_prefix=f'{api_prefix}/dashboard') app.register_blueprint(applications_bp, url_prefix=f'{api_prefix}/applications') app.register_blueprint(knowledgebase_bp, url_prefix=f'{api_prefix}/knowledgebase') app.register_blueprint(search_bp, url_prefix=f'{api_prefix}/search') app.register_blueprint(reports_bp, url_prefix=f'{api_prefix}/reports') app.register_blueprint(collector_bp, url_prefix=f'{api_prefix}/collector') app.register_blueprint(employees_bp, url_prefix=f'{api_prefix}/employees') app.register_blueprint(slides_bp, url_prefix=f'{api_prefix}/slides') app.register_blueprint(settings_bp, url_prefix=f'{api_prefix}/settings') app.register_blueprint(auditlogs_bp, url_prefix=f'{api_prefix}/auditlogs') app.register_blueprint(users_bp, url_prefix=f'{api_prefix}/users') def register_cli_commands(app: Flask): """Register Flask CLI commands.""" from .plugins.cli import plugin_cli from .cli import db_cli, seed_cli app.cli.add_command(plugin_cli) app.cli.add_command(db_cli) app.cli.add_command(seed_cli) def register_error_handlers(app: Flask): """Register error handlers.""" from .utils.responses import error_response, ErrorCodes from .exceptions import ShopDBException @app.errorhandler(ShopDBException) def handle_shopdb_exception(error): http_codes = { 'NOT_FOUND': 404, 'UNAUTHORIZED': 401, 'FORBIDDEN': 403, 'CONFLICT': 409, 'VALIDATION_ERROR': 400, } http_code = http_codes.get(error.code, 400) return error_response( error.code, error.message, details=error.details, http_code=http_code ) @app.errorhandler(404) def not_found_error(error): return error_response( ErrorCodes.NOT_FOUND, 'Resource not found', http_code=404 ) @app.errorhandler(500) def internal_error(error): return error_response( ErrorCodes.INTERNAL_ERROR, 'An internal error occurred', http_code=500 ) @app.errorhandler(401) def unauthorized_error(error): return error_response( ErrorCodes.UNAUTHORIZED, 'Authentication required', http_code=401 ) def register_frontend_routes(app: Flask): """Serve Vue frontend static files.""" frontend_dist = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'frontend', 'dist') @app.route('/', defaults={'path': ''}) @app.route('/') def serve_frontend(path): # Don't serve API routes as frontend if path.startswith('api/'): from .utils.responses import error_response, ErrorCodes return error_response(ErrorCodes.NOT_FOUND, 'API endpoint not found', http_code=404) # Serve static assets if path and os.path.exists(os.path.join(frontend_dist, path)): return send_from_directory(frontend_dist, path) # Serve index.html for SPA routing return send_from_directory(frontend_dist, 'index.html') def configure_logging(app: Flask): """Configure application logging.""" log_level = app.config.get('LOG_LEVEL', 'INFO') logging.basicConfig( level=getattr(logging, log_level), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' )