178 lines
5.2 KiB
Python
178 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from sqlmodel import Session
|
|
|
|
from ..connectors.radarr import RadarrConnector
|
|
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/radarr', dependencies=[Depends(load_connector_runtime)])
|
|
|
|
|
|
_ADD_MOVIE_STEPS = [
|
|
'Validate payload',
|
|
'Evaluate policy',
|
|
'Resolve root folder / quality profile defaults',
|
|
'Add movie in Radarr',
|
|
'Optional search movie',
|
|
'Verify movie exists',
|
|
'Complete audit/job',
|
|
]
|
|
|
|
|
|
@router.get('/health')
|
|
def health():
|
|
return RadarrConnector().health()
|
|
|
|
|
|
@router.get('/queue')
|
|
def queue():
|
|
return RadarrConnector().queue_status()
|
|
|
|
|
|
@router.get('/wanted')
|
|
def wanted():
|
|
return RadarrConnector().wanted_missing()
|
|
|
|
|
|
@router.get('/search')
|
|
def search(q: str = ''):
|
|
query = q.strip()
|
|
if not query:
|
|
return {'success': False, 'status': 'error', 'message': 'query_required', 'items': []}
|
|
return RadarrConnector().search_movie(query)
|
|
|
|
|
|
@router.post('/movie')
|
|
def add_movie(payload: dict, session: Session = Depends(get_session)):
|
|
action = 'radarr.add_movie'
|
|
target = 'radarr'
|
|
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', 'low'))
|
|
|
|
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': _ADD_MOVIE_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': _ADD_MOVIE_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=_ADD_MOVIE_STEPS,
|
|
)
|
|
start_job(session, job)
|
|
set_job_progress(session, job, 35, 'Payload validated and policy allowed.')
|
|
|
|
connector_result = RadarrConnector().add_movie(payload)
|
|
success = bool(connector_result.get('success'))
|
|
result_status = 'succeeded' if success else 'failed'
|
|
message = str(connector_result.get('message', 'movie_added' if success else 'radarr_failed'))
|
|
|
|
completed = complete_job(
|
|
session,
|
|
job,
|
|
status=result_status,
|
|
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': _ADD_MOVIE_STEPS, 'jobId': completed.id},
|
|
after_state={'status': connector_result.get('status'), 'message': message},
|
|
redacted_metadata={'connector': 'radarr'},
|
|
)
|
|
|
|
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', {}),
|
|
}
|