from fastapi import APIRouter, Depends, HTTPException, status from admin_app.api.dependencies import ( get_settings, get_tool_management_service, require_panel_admin_permission, ) from admin_app.api.schemas import ( AdminToolContractsResponse, AdminToolDraftIntakeRequest, AdminToolDraftIntakeResponse, AdminToolDraftListResponse, AdminToolGenerationPipelineResponse, AdminToolGovernanceTransitionResponse, AdminToolManagementActionResponse, AdminToolOverviewResponse, AdminToolPublicationListResponse, AdminToolReviewQueueResponse, ) from admin_app.core import AdminSettings, AuthenticatedStaffPrincipal from admin_app.services import ToolManagementService from shared.contracts import AdminPermission, StaffRole, role_has_permission router = APIRouter(prefix="/panel/tools", tags=["panel-tools"]) @router.get( "/overview", response_model=AdminToolOverviewResponse, ) def panel_tools_overview( settings: AdminSettings = Depends(get_settings), service: ToolManagementService = Depends(get_tool_management_service), current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS) ), ): payload = service.build_overview_payload() return AdminToolOverviewResponse( service="orquestrador-admin", mode=payload["mode"], metrics=payload["metrics"], workflow=payload["workflow"], actions=_build_panel_actions(settings, current_staff.role), next_steps=payload["next_steps"], ) @router.get( "/contracts", response_model=AdminToolContractsResponse, ) def panel_tool_contracts( service: ToolManagementService = Depends(get_tool_management_service), _current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS) ), ): payload = service.build_contracts_payload() return AdminToolContractsResponse( service="orquestrador-admin", publication_source_service=payload["publication_source_service"], publication_target_service=payload["publication_target_service"], lifecycle_statuses=payload["lifecycle_statuses"], parameter_types=payload["parameter_types"], publication_fields=payload["publication_fields"], published_tool_fields=payload["published_tool_fields"], ) @router.get( "/drafts", response_model=AdminToolDraftListResponse, ) def panel_tool_drafts( service: ToolManagementService = Depends(get_tool_management_service), _current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS) ), ): payload = service.build_drafts_payload() return AdminToolDraftListResponse( service="orquestrador-admin", storage_status=payload["storage_status"], message=payload["message"], drafts=payload["drafts"], supported_statuses=payload["supported_statuses"], ) @router.post( "/drafts/intake", response_model=AdminToolDraftIntakeResponse, ) def panel_tool_draft_intake( draft: AdminToolDraftIntakeRequest, service: ToolManagementService = Depends(get_tool_management_service), current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS) ), ): try: payload = service.create_draft_submission( draft.model_dump(), owner_staff_account_id=current_staff.id, owner_name=current_staff.display_name, owner_role=current_staff.role, ) except ValueError as exc: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc), ) from exc return AdminToolDraftIntakeResponse( service="orquestrador-admin", storage_status=payload["storage_status"], message=payload["message"], submission_policy=payload["submission_policy"], draft_preview=payload["draft_preview"], warnings=payload["warnings"], next_steps=payload["next_steps"], ) @router.post( "/pipeline/{version_id}/run", response_model=AdminToolGenerationPipelineResponse, ) def panel_tool_pipeline_run( version_id: str, service: ToolManagementService = Depends(get_tool_management_service), current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.MANAGE_TOOL_DRAFTS) ), ): try: payload = service.run_generation_pipeline( version_id, runner_staff_account_id=current_staff.id, runner_name=current_staff.display_name, runner_role=current_staff.role, ) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc except PermissionError as exc: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc return _build_pipeline_response(payload) @router.get( "/review-queue", response_model=AdminToolReviewQueueResponse, ) def panel_tool_review_queue( service: ToolManagementService = Depends(get_tool_management_service), _current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.REVIEW_TOOL_GENERATIONS) ), ): payload = service.build_review_queue_payload() return AdminToolReviewQueueResponse( service="orquestrador-admin", queue_mode=payload["queue_mode"], message=payload["message"], items=payload["items"], supported_statuses=payload["supported_statuses"], ) @router.post( "/review-queue/{version_id}/review", response_model=AdminToolGovernanceTransitionResponse, ) def panel_tool_review_queue_review( version_id: str, service: ToolManagementService = Depends(get_tool_management_service), current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.REVIEW_TOOL_GENERATIONS) ), ): try: payload = service.review_version( version_id, reviewer_staff_account_id=current_staff.id, reviewer_name=current_staff.display_name, reviewer_role=current_staff.role, ) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc except PermissionError as exc: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc return _build_governance_transition_response(payload) @router.post( "/review-queue/{version_id}/approve", response_model=AdminToolGovernanceTransitionResponse, ) def panel_tool_review_queue_approve( version_id: str, service: ToolManagementService = Depends(get_tool_management_service), current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.REVIEW_TOOL_GENERATIONS) ), ): try: payload = service.approve_version( version_id, approver_staff_account_id=current_staff.id, approver_name=current_staff.display_name, approver_role=current_staff.role, ) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc except PermissionError as exc: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc return _build_governance_transition_response(payload) @router.get( "/publications", response_model=AdminToolPublicationListResponse, ) def panel_tool_publications( service: ToolManagementService = Depends(get_tool_management_service), _current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.PUBLISH_TOOLS) ), ): payload = service.build_publications_payload() return AdminToolPublicationListResponse( service="orquestrador-admin", source=payload["source"], target_service=payload["target_service"], publications=payload["publications"], ) @router.post( "/publications/{version_id}/publish", response_model=AdminToolGovernanceTransitionResponse, ) def panel_tool_publications_publish( version_id: str, service: ToolManagementService = Depends(get_tool_management_service), current_staff: AuthenticatedStaffPrincipal = Depends( require_panel_admin_permission(AdminPermission.PUBLISH_TOOLS) ), ): try: payload = service.publish_version( version_id, publisher_staff_account_id=current_staff.id, publisher_name=current_staff.display_name, publisher_role=current_staff.role, ) except LookupError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc except PermissionError as exc: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(exc)) from exc except ValueError as exc: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc return _build_governance_transition_response(payload) def _build_pipeline_response(payload: dict) -> AdminToolGenerationPipelineResponse: return AdminToolGenerationPipelineResponse( service="orquestrador-admin", message=payload["message"], version_id=payload["version_id"], tool_name=payload["tool_name"], version_number=payload["version_number"], status=payload["status"], current_step=payload["current_step"], steps=payload["steps"], queue_entry=payload["queue_entry"], automated_validations=payload.get("automated_validations", []), next_steps=payload["next_steps"], ) def _build_governance_transition_response(payload: dict) -> AdminToolGovernanceTransitionResponse: return AdminToolGovernanceTransitionResponse( service="orquestrador-admin", message=payload["message"], version_id=payload["version_id"], tool_name=payload["tool_name"], version_number=payload["version_number"], status=payload["status"], queue_entry=payload["queue_entry"], publication=payload["publication"], next_steps=payload["next_steps"], ) def _build_panel_actions( settings: AdminSettings, current_role: StaffRole | str | None = None, ) -> list[AdminToolManagementActionResponse]: actions = [ AdminToolManagementActionResponse( key="overview", label="Overview web de tools", href=_build_prefixed_path(settings.admin_api_prefix, "/panel/tools/overview"), required_permission=AdminPermission.MANAGE_TOOL_DRAFTS, description="Snapshot do dominio de tools pronto para leitura no painel.", ), AdminToolManagementActionResponse( key="contracts", label="Contratos web de tools", href=_build_prefixed_path(settings.admin_api_prefix, "/panel/tools/contracts"), required_permission=AdminPermission.MANAGE_TOOL_DRAFTS, description="Base contratual para a tela de revisao e aprovacao.", ), AdminToolManagementActionResponse( key="draft_intake", label="Pre-cadastro web de tool", href=_build_prefixed_path(settings.admin_api_prefix, "/panel/tools/drafts/intake"), required_permission=AdminPermission.MANAGE_TOOL_DRAFTS, description="Valida e persiste o draft diretamente na sessao web do painel.", ), AdminToolManagementActionResponse( key="review_queue", label="Fila web de revisao", href=_build_prefixed_path(settings.admin_api_prefix, "/panel/tools/review-queue"), required_permission=AdminPermission.REVIEW_TOOL_GENERATIONS, description="Leitura da fila de revisao sob a sessao web do painel.", ), AdminToolManagementActionResponse( key="publications", label="Publicacoes web", href=_build_prefixed_path(settings.admin_api_prefix, "/panel/tools/publications"), required_permission=AdminPermission.PUBLISH_TOOLS, description="Catalogo de tools ativas e prontas para ativacao no produto.", ), ] if current_role is None: return actions return [action for action in actions if role_has_permission(current_role, action.required_permission)] def _build_prefixed_path(api_prefix: str, path: str) -> str: normalized_prefix = api_prefix.rstrip("/") normalized_path = path if path.startswith("/") else f"/{path}" if not normalized_prefix: return normalized_path if normalized_path == "/": return f"{normalized_prefix}/" return f"{normalized_prefix}{normalized_path}"