"""Plugin introspection + enable/disable API.""" from flask import Blueprint, current_app, request from flask_jwt_extended import jwt_required from shopdb.utils.responses import success_response, error_response, ErrorCodes from shopdb.utils.authz import require_permission, require_role plugins_bp = Blueprint('plugins', __name__) @plugins_bp.route('', methods=['GET']) @jwt_required(optional=True) def list_plugins(): """List all discovered plugins (enabled or not) with their metadata and the framework contract version.""" from shopdb import __contract_version__ pm = current_app.extensions.get('plugin_manager') plugins = pm.discover_available() if pm else [] plugins.sort(key=lambda p: p['name']) return success_response({ 'contract_version': __contract_version__, 'count': len(plugins), 'plugins': plugins, }) @plugins_bp.route('/enabled', methods=['GET']) @jwt_required(optional=True) def list_enabled_plugins(): """Return just the names of enabled plugins as a flat array. Cheap registry read (no DB). Exposed to anonymous callers on purpose: the navigation endpoint already leaks the same enabled/disabled signal, and the frontend needs it (including unauthenticated kiosk routes like /tv) to gate plugin-owned routes. No metadata beyond the names. """ pm = current_app.extensions.get('plugin_manager') names = pm.registry.get_enabled_plugins() if pm else [] return success_response(sorted(names)) @plugins_bp.route('/', methods=['PUT']) @jwt_required() @require_role('admin') def set_plugin_enabled(name: str): """Enable or disable a plugin. Takes effect on the next app restart for route/navigation changes.""" data = request.get_json() or {} if 'enabled' not in data: return error_response(ErrorCodes.VALIDATION_ERROR, 'enabled is required') pm = current_app.extensions.get('plugin_manager') if not pm: return error_response(ErrorCodes.INTERNAL_ERROR, 'Plugin manager unavailable', http_code=500) want = bool(data['enabled']) ok = pm.enable_plugin(name) if want else pm.disable_plugin(name) if not ok: # enable/disable refused (unknown plugin, or a dependency conflict) return error_response( ErrorCodes.CONFLICT, f"Could not {'enable' if want else 'disable'} '{name}' " f"(check it exists and dependencies allow it)", http_code=409 ) return success_response( {'name': name, 'enabled': want}, message=f"Plugin {'enabled' if want else 'disabled'} " f"(restart to apply route changes)" )