Files
Unraid-AppOps-Gateway-Public/backend/app/api/routes_plex.py
T

170 lines
5.9 KiB
Python

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', {})}