from __future__ import annotations from collections.abc import Callable from typing import Any from fastapi import APIRouter, Depends from sqlmodel import Session from ..connectors.widefrog import WidefrogConnector 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/widefrog', dependencies=[Depends(load_connector_runtime)]) _RETRY_STEPS = [ 'Validate connector config', 'Evaluate policy', 'Fetch failed jobs summary', 'Call retry endpoint', 'Fetch queue/activity summary', 'Complete audit/job', ] _SCAN_STEPS = [ 'Validate connector config', 'Evaluate policy', 'Check app health', 'Call scan endpoint', 'Verify accepted or status', 'Complete audit/job', ] @router.get('/health') def health(): return WidefrogConnector().health() @router.get('/queue') def queue(): return WidefrogConnector().queue_status() @router.get('/failed') def failed(): return WidefrogConnector().failed_jobs() @router.get('/activity') def activity(): return WidefrogConnector().activity() @router.get('/download-status') def download_status(): return WidefrogConnector().download_status() @router.post('/retry-failed') def retry_failed(payload: dict | None = None, session: Session = Depends(get_session)): return _execute_widefrog_action( session=session, payload=payload or {}, action='widefrog.retry_failed', steps=_RETRY_STEPS, connector_call=lambda connector, body: connector.retry_failed(body), default_success_message='retry_started', ) @router.post('/scan-library') def scan_library(payload: dict | None = None, session: Session = Depends(get_session)): return _execute_widefrog_action( session=session, payload=payload or {}, action='widefrog.scan_library', steps=_SCAN_STEPS, connector_call=lambda connector, body: connector.scan_library(body), default_success_message='scan_started', ) def _execute_widefrog_action( *, session: Session, payload: dict[str, Any], action: str, steps: list[str], connector_call: Callable[[WidefrogConnector, dict[str, Any]], dict[str, Any]], default_success_message: str, ) -> dict[str, Any]: target = 'widefrog' 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']}, ) 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={'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 Widefrog connector.') connector_result = connector_call(WidefrogConnector(), payload) success = bool(connector_result.get('success')) message = str(connector_result.get('message', default_success_message if success else 'widefrog_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={'connector': 'widefrog'}, ) 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', {})}