| 1 | from fastapi import APIRouter |
| 2 | from fastapi import Depends |
| 3 | from fastapi import HTTPException |
| 4 | from fastapi import Security |
| 5 | from loguru import logger |
| 6 | from sqlalchemy.ext.asyncio import AsyncSession |
| 7 | |
| 8 | from app.auth.utils import AuthHandler |
| 9 | from app.connectors.shuffle.schema.integrations import ExecuteWorkflowRequest |
| 10 | from app.connectors.shuffle.schema.integrations import IntegrationRequest |
| 11 | from app.connectors.shuffle.schema.integrations import ( |
| 12 | ShuffleConnectorCredentialsResponse, |
| 13 | ) |
| 14 | from app.connectors.shuffle.services.integrations import execute_integration |
| 15 | from app.connectors.shuffle.services.integrations import execute_workflow |
| 16 | from app.connectors.utils import get_connector_info_from_db |
| 17 | from app.db.db_session import get_db |
| 18 | |
| 19 | shuffle_integrations_router = APIRouter() |
| 20 | |
| 21 | |
| 22 | @shuffle_integrations_router.get( |
| 23 | "/credentials", |
| 24 | response_model=ShuffleConnectorCredentialsResponse, |
| 25 | description="Return the Shuffle connector base URL + API key for the embedded MCP picker.", |
| 26 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 27 | ) |
| 28 | async def get_shuffle_connector_credentials( |
| 29 | session: AsyncSession = Depends(get_db), |
| 30 | ) -> ShuffleConnectorCredentialsResponse: |
| 31 | """Return the deployment-wide Shuffle connector URL + API key for the |
| 32 | embedded `<ShuffleMCP>` / `<TryMcpSection>` / `<AppDetailDrawer>` |
| 33 | React components. The browser talks to Shuffle directly now that the |
| 34 | Shuffle team has fixed CORS on their backends — base_url is the real |
| 35 | connector URL (e.g. https://shuffler.io or a regional/self-hosted |
| 36 | deployment), not a same-origin proxy path.""" |
| 37 | info = await get_connector_info_from_db("Shuffle", session) |
| 38 | if not info: |
| 39 | raise HTTPException(status_code=404, detail="Shuffle connector is not configured.") |
| 40 | api_key = info.get("connector_api_key") |
| 41 | real_base_url = info.get("connector_url") |
| 42 | if not api_key or not real_base_url: |
| 43 | raise HTTPException( |
| 44 | status_code=400, |
| 45 | detail="Shuffle connector is missing connector_url or connector_api_key.", |
| 46 | ) |
| 47 | return ShuffleConnectorCredentialsResponse( |
| 48 | success=True, |
| 49 | message="Shuffle connector credentials retrieved.", |
| 50 | base_url=real_base_url.rstrip("/"), |
| 51 | api_key=api_key, |
| 52 | ) |
| 53 | |
| 54 | |
| 55 | @shuffle_integrations_router.post( |
| 56 | "/execute", |
| 57 | description="Execute a Shuffle Integration.", |
| 58 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 59 | ) |
| 60 | async def execute_integration_route(request: IntegrationRequest): |
| 61 | """ |
| 62 | Execute a workflow. |
| 63 | |
| 64 | Args: |
| 65 | request (IntegrationRequest): The request object containing the workflow ID. |
| 66 | |
| 67 | Returns: |
| 68 | dict: The response containing the execution ID. |
| 69 | """ |
| 70 | logger.info("Executing workflow") |
| 71 | return await execute_integration(request) |
| 72 | |
| 73 | |
| 74 | @shuffle_integrations_router.post( |
| 75 | "/invoke-workflow", |
| 76 | description="Invoke a Shuffle Workflow.", |
| 77 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 78 | ) |
| 79 | async def invoke_workflow_route(request: ExecuteWorkflowRequest): |
| 80 | """ |
| 81 | Execute a workflow. |
| 82 | |
| 83 | Args: |
| 84 | request (IntegrationRequest): The request object containing the workflow ID. |
| 85 | |
| 86 | Returns: |
| 87 | dict: The response containing the execution ID. |
| 88 | """ |
| 89 | logger.info("Executing workflow") |
| 90 | return await execute_workflow(request) |