from __future__ import annotations from collections.abc import Callable from typing import Any from fastapi import APIRouter, Depends from sqlmodel import Session from ..connectors.plex import PlexConnector from ..db.session import get_session from .connector_runtime import load_connector_runtime from ..services.approval_service import create_approval from ..services.audit_logger import log_event from ..services.job_runner import complete_job, create_job, set_job_progress, start_job from ..services.policy_engine import evaluate_policy router = APIRouter(prefix='/api/apps/plex', dependencies=[Depends(load_connector_runtime)]) _SCAN_LIBRARY_STEPS = [ 'Validate library id', 'Evaluate policy', 'Check Plex health', 'Fetch library status', 'Trigger scan library', 'Verify accepted or return status', 'Complete audit/job', ] _SCAN_ALL_STEPS = [ 'Evaluate policy', 'Check Plex health', 'Fetch libraries', 'Trigger scan for each library', 'Complete audit/job', ] @router.get('/health') def health(): return PlexConnector().health() @router.get('/server') def server(): return PlexConnector().server_status() @router.get('/libraries') def libraries(): return PlexConnector().library_status() @router.get('/sessions') def sessions(): return PlexConnector().active_sessions() @router.get('/recent') def recent(): return PlexConnector().recent_media() @router.get('/streams') def streams(): return PlexConnector().stream_summary() @router.post('/libraries/{library_id}/scan') def scan_library(library_id: str, payload: dict | None = None, session: Session = Depends(get_session)): return _execute_plex_action( session=session, payload=payload or {}, action='plex.scan_library', steps=_SCAN_LIBRARY_STEPS, connector_call=lambda connector: connector.scan_library(library_id), default_success_message='scan_started', metadata={'libraryId': library_id}, ) @router.post('/libraries/scan-all') def scan_all_libraries(payload: dict | None = None, session: Session = Depends(get_session)): return _execute_plex_action( session=session, payload=payload or {}, action='plex.scan_all_libraries', steps=_SCAN_ALL_STEPS, connector_call=lambda connector: connector.scan_all_libraries(), default_success_message='scan_all_started', metadata={'scope': 'all'}, ) def _execute_plex_action( *, session: Session, payload: dict[str, Any], action: str, steps: list[str], connector_call: Callable[[PlexConnector], dict[str, Any]], default_success_message: str, metadata: dict[str, Any], ) -> dict[str, Any]: target = 'plex' source = str(payload.get('source', 'manual')).strip() or 'manual' policy = evaluate_policy(session=session, action=action, target=target, source=source) risk = str(policy.get('risk', 'medium')) if policy['decision'] == 'blocked': audit = log_event( session, source='action', identity=source, action_id=action, target=target, risk=risk, policy_decision='blocked', reason=policy['reason'], result='blocked', execution_plan={'plan': steps, 'policyReason': policy['reason']}, redacted_metadata=metadata, ) return {'success': False, 'decision': 'blocked', 'status': 'failed', 'policy_decision': 'blocked', 'result': 'blocked', 'message': policy['reason'], 'auditId': audit.id} if policy['decision'] == 'approval_required': approval = create_approval(session, action=action, target=target, source=source, risk=risk, reason=policy['reason'], requester=source) audit = log_event( session, source='action', identity=source, action_id=action, target=target, risk=risk, policy_decision='approval_required', reason=policy['reason'], result='approval_requested', execution_plan={'plan': steps, 'approvalId': approval.id}, redacted_metadata={**metadata, 'approval_id': approval.id}, ) return {'success': False, 'decision': 'approval_required', 'status': 'failed', 'policy_decision': 'approval_required', 'result': 'approval_required', 'message': 'Action requires approval before execution.', 'approvalId': approval.id, 'auditId': audit.id} job = create_job(session, action_id=action, target=target, source=source, risk=risk, policy_decision='allowed', steps=steps) start_job(session, job) set_job_progress(session, job, 35, 'Policy allowed; calling Plex connector.') connector_result = connector_call(PlexConnector()) success = bool(connector_result.get('success')) message = str(connector_result.get('message', default_success_message if success else 'plex_failed')) completed = complete_job(session, job, status='succeeded' if success else 'failed', result_summary=message) audit = log_event( session, source='action', identity=source, action_id=action, target=target, risk=risk, policy_decision='allowed', reason=policy['reason'], result='success' if success else 'failed', execution_plan={'plan': steps, 'jobId': completed.id}, after_state={'status': connector_result.get('status'), 'message': message}, redacted_metadata={**metadata, 'connector': 'plex'}, ) if not success: return {'success': False, 'decision': 'failed', 'status': 'failed', 'policy_decision': 'allowed', 'result': 'failed', 'message': message, 'jobId': completed.id, 'auditId': audit.id, 'data': connector_result.get('data')} return {'success': True, 'decision': 'allowed', 'status': 'success', 'policy_decision': 'allowed', 'result': 'success', 'message': message, 'jobId': completed.id, 'auditId': audit.id, 'data': connector_result.get('data', {})}