Files
shopdb-flask/shopdb/core/api/plugins.py

76 lines
2.7 KiB
Python

"""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_role
from shopdb.utils.authz import require_permission
plugins_bp = Blueprint('plugins', __name__)
@plugins_bp.route('', methods=['GET'])
@require_permission('settings.view')
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('/<name>', 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)"
)