from __future__ import annotations from collections.abc import Callable from typing import Any from fastapi import APIRouter, Depends from sqlmodel import Session from ..connectors.deluge import DelugeConnector from ..db.session import get_session 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 from .connector_runtime import load_connector_runtime router = APIRouter(prefix='/api/apps/deluge', dependencies=[Depends(load_connector_runtime)]) _PAUSE_STEPS = [ 'Validate torrent id', 'Evaluate policy', 'Check Deluge health/session', 'Pause torrent', 'Fetch torrent status', 'Complete audit/job', ] _RESUME_STEPS = [ 'Validate torrent id', 'Evaluate policy', 'Check Deluge health/session', 'Resume torrent', 'Fetch torrent status', 'Complete audit/job', ] _BULK_STEPS = [ 'Validate action scope', 'Evaluate policy', 'Require operator approval', 'Call Deluge bulk operation', 'Complete audit/job', ] @router.get('/health') def health(): return DelugeConnector().health() @router.get('/session') def session_status(): return DelugeConnector().session_status() @router.get('/torrents') def torrents(): return DelugeConnector().torrent_status() @router.get('/active') def active(): return DelugeConnector().active_downloads() @router.get('/stalled') def stalled(): return DelugeConnector().stalled_downloads() @router.get('/paused') def paused(): return DelugeConnector().paused_downloads() @router.get('/speeds') def speeds(): return DelugeConnector().speed_status() @router.post('/torrents/{torrent_id}/pause') def pause_torrent(torrent_id: str, payload: dict | None = None, session: Session = Depends(get_session)): return _execute_deluge_action( session=session, payload=payload or {}, action='deluge.pause_torrent', steps=_PAUSE_STEPS, connector_call=lambda connector: connector.pause_torrent(torrent_id), default_success_message='torrent_paused', metadata={'torrentId': torrent_id}, ) @router.post('/torrents/{torrent_id}/resume') def resume_torrent(torrent_id: str, payload: dict | None = None, session: Session = Depends(get_session)): return _execute_deluge_action( session=session, payload=payload or {}, action='deluge.resume_torrent', steps=_RESUME_STEPS, connector_call=lambda connector: connector.resume_torrent(torrent_id), default_success_message='torrent_resumed', metadata={'torrentId': torrent_id}, ) @router.post('/pause-all') def pause_all(payload: dict | None = None, session: Session = Depends(get_session)): return _execute_deluge_action( session=session, payload=payload or {}, action='deluge.pause_all', steps=_BULK_STEPS, connector_call=lambda connector: connector.pause_all(), default_success_message='all_torrents_paused', metadata={'scope': 'all'}, ) @router.post('/resume-all') def resume_all(payload: dict | None = None, session: Session = Depends(get_session)): return _execute_deluge_action( session=session, payload=payload or {}, action='deluge.resume_all', steps=_BULK_STEPS, connector_call=lambda connector: connector.resume_all(), default_success_message='all_torrents_resumed', metadata={'scope': 'all'}, ) def _execute_deluge_action( *, session: Session, payload: dict[str, Any], action: str, steps: list[str], connector_call: Callable[[DelugeConnector], dict[str, Any]], default_success_message: str, metadata: dict[str, Any], ) -> dict[str, Any]: target = 'deluge' 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 Deluge connector.') connector_result = connector_call(DelugeConnector()) success = bool(connector_result.get('success')) message = str(connector_result.get('message', default_success_message if success else 'deluge_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': 'deluge'}, ) 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', {})}