""" Service Store Blueprint — /api/store Provides routes to browse, install, and remove services from the PIC service store. Authentication is enforced by the global before_request hook in app.py (admin session required for all /api/* routes except /api/auth/*). """ import logging from flask import Blueprint, request, jsonify import requests as _requests from service_store_manager import MANIFEST_URL_TPL logger = logging.getLogger('picell') store_bp = Blueprint('service_store', __name__, url_prefix='/api/store') def _ssm(): """Lazy import of service_store_manager to avoid circular import at module load.""" from app import service_store_manager return service_store_manager def _cfg(): from app import config_manager return config_manager @store_bp.route('/services', methods=['GET']) def list_store_services(): """Return available and installed services.""" try: return jsonify(_ssm().list_services()) except Exception as e: logger.error(f'list_store_services: {e}') return jsonify({'error': str(e)}), 500 @store_bp.route('/services//manifest', methods=['GET']) def get_manifest(service_id: str): """Fetch and return the manifest for a specific service.""" try: url = MANIFEST_URL_TPL.format(id=service_id) resp = _requests.get(url, timeout=10) resp.raise_for_status() return jsonify(resp.json()) except _requests.HTTPError as e: return jsonify({'error': f'Manifest not found: {e}'}), 404 except Exception as e: logger.error(f'get_manifest({service_id}): {e}') return jsonify({'error': str(e)}), 500 @store_bp.route('/services//install', methods=['POST']) def install_service(service_id: str): """Install a service from the store.""" try: result = _ssm().install(service_id) if result.get('ok'): return jsonify(result) return jsonify(result), 400 except Exception as e: logger.error(f'install_service({service_id}): {e}') return jsonify({'error': str(e)}), 500 @store_bp.route('/services/', methods=['DELETE']) def remove_service(service_id: str): """Remove an installed service.""" try: purge = request.args.get('purge') == 'true' result = _ssm().remove(service_id, purge_data=purge) if result.get('ok'): return jsonify(result) return jsonify(result), 404 except Exception as e: logger.error(f'remove_service({service_id}): {e}') return jsonify({'error': str(e)}), 500 @store_bp.route('/installed', methods=['GET']) def get_installed(): """Return all currently installed services.""" try: return jsonify({'installed': _cfg().get_installed_services()}) except Exception as e: logger.error(f'get_installed: {e}') return jsonify({'error': str(e)}), 500 @store_bp.route('/refresh', methods=['POST']) def refresh_index(): """Invalidate the index cache and return a fresh service list.""" try: ssm = _ssm() ssm._index_cache = None ssm._index_cache_time = 0 return jsonify(ssm.list_services()) except Exception as e: logger.error(f'refresh_index: {e}') return jsonify({'error': str(e)}), 500