suna/backend/pipedream/api.py

729 lines
25 KiB
Python
Raw Permalink Normal View History

2025-07-08 21:18:49 +08:00
from fastapi import APIRouter, HTTPException, Depends, Query
2025-07-08 17:10:15 +08:00
from typing import List, Dict, Any, Optional
from pydantic import BaseModel
2025-07-14 18:36:27 +08:00
from uuid import UUID
from datetime import datetime
2025-07-08 17:10:15 +08:00
from utils.logger import logger
from utils.auth_utils import get_current_user_id_from_jwt
2025-07-30 22:03:43 +08:00
from .profile_service import ProfileService, Profile, ProfileServiceError, ProfileNotFoundError, ProfileAlreadyExistsError, InvalidConfigError, EncryptionError
2025-07-30 20:27:26 +08:00
from .connection_service import ConnectionService
2025-07-30 22:22:31 +08:00
from .app_service import get_app_service
2025-07-30 22:03:43 +08:00
from .mcp_service import MCPService, ConnectionStatus, MCPConnectionError, MCPServiceError
2025-07-30 20:27:26 +08:00
from .connection_token_service import ConnectionTokenService
2025-07-21 18:33:00 +08:00
import httpx
import json
2025-07-08 17:10:15 +08:00
router = APIRouter(prefix="/pipedream", tags=["pipedream"])
2025-07-14 18:36:27 +08:00
2025-07-30 20:27:26 +08:00
profile_service: Optional[ProfileService] = None
connection_service: Optional[ConnectionService] = None
2025-07-30 22:22:31 +08:00
app_service = None
2025-07-30 20:27:26 +08:00
mcp_service: Optional[MCPService] = None
connection_token_service: Optional[ConnectionTokenService] = None
2025-07-09 02:40:58 +08:00
def initialize(database):
2025-07-30 20:27:26 +08:00
pass
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
class CreateConnectionTokenRequest(BaseModel):
app: Optional[str] = None
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
class ConnectionTokenResponse(BaseModel):
success: bool
link: Optional[str] = None
token: Optional[str] = None
2025-07-14 18:36:27 +08:00
external_user_id: str = ""
2025-07-08 17:10:15 +08:00
app: Optional[str] = None
expires_at: Optional[str] = None
error: Optional[str] = None
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
class ConnectionResponse(BaseModel):
success: bool
connections: List[Dict[str, Any]]
count: int
error: Optional[str] = None
class MCPDiscoveryRequest(BaseModel):
app_slug: Optional[str] = None
oauth_app_id: Optional[str] = None
2025-07-14 18:36:27 +08:00
2025-07-09 02:40:58 +08:00
class MCPProfileDiscoveryRequest(BaseModel):
external_user_id: str
app_slug: Optional[str] = None
oauth_app_id: Optional[str] = None
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
class MCPDiscoveryResponse(BaseModel):
success: bool
mcp_servers: List[Dict[str, Any]]
count: int
error: Optional[str] = None
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
class MCPConnectionRequest(BaseModel):
app_slug: str
oauth_app_id: Optional[str] = None
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
class MCPConnectionResponse(BaseModel):
success: bool
mcp_config: Optional[Dict[str, Any]] = None
error: Optional[str] = None
2025-07-14 18:36:27 +08:00
class ProfileRequest(BaseModel):
profile_name: str
app_slug: str
app_name: str
description: Optional[str] = None
is_default: bool = False
oauth_app_id: Optional[str] = None
enabled_tools: List[str] = []
external_user_id: Optional[str] = None
class UpdateProfileRequest(BaseModel):
profile_name: Optional[str] = None
display_name: Optional[str] = None
is_active: Optional[bool] = None
is_default: Optional[bool] = None
enabled_tools: Optional[List[str]] = None
class ProfileResponse(BaseModel):
profile_id: UUID
account_id: UUID
mcp_qualified_name: str
profile_name: str
display_name: str
app_slug: str
app_name: str
external_user_id: str
enabled_tools: List[str]
is_active: bool
is_default: bool
is_connected: bool
created_at: datetime
updated_at: datetime
last_used_at: Optional[datetime] = None
@classmethod
def from_domain(cls, profile: Profile) -> 'ProfileResponse':
return cls(
profile_id=profile.profile_id,
account_id=profile.account_id,
mcp_qualified_name=profile.mcp_qualified_name,
2025-07-30 22:03:43 +08:00
profile_name=profile.profile_name,
2025-07-14 18:36:27 +08:00
display_name=profile.display_name,
2025-07-30 22:03:43 +08:00
app_slug=profile.app_slug,
2025-07-14 18:36:27 +08:00
app_name=profile.app_name,
2025-07-30 22:03:43 +08:00
external_user_id=profile.external_user_id,
2025-07-14 18:36:27 +08:00
enabled_tools=profile.enabled_tools,
is_active=profile.is_active,
is_default=profile.is_default,
is_connected=profile.is_connected,
created_at=profile.created_at,
updated_at=profile.updated_at,
last_used_at=profile.last_used_at
)
def _strip_pipedream_prefix(app_slug: Optional[str]) -> Optional[str]:
if app_slug and app_slug.startswith("pipedream:"):
return app_slug[len("pipedream:"):]
return app_slug
def _handle_pipedream_exception(e: Exception) -> HTTPException:
if isinstance(e, ProfileNotFoundError):
return HTTPException(status_code=404, detail=str(e))
elif isinstance(e, ProfileAlreadyExistsError):
return HTTPException(status_code=409, detail=str(e))
2025-07-30 22:03:43 +08:00
elif isinstance(e, InvalidConfigError):
2025-07-14 18:36:27 +08:00
return HTTPException(status_code=400, detail=str(e))
2025-07-30 22:03:43 +08:00
elif isinstance(e, EncryptionError):
return HTTPException(status_code=500, detail=str(e))
2025-07-14 18:36:27 +08:00
elif isinstance(e, MCPConnectionError):
return HTTPException(status_code=502, detail=str(e))
2025-07-30 22:03:43 +08:00
elif isinstance(e, MCPServiceError):
return HTTPException(status_code=500, detail=str(e))
elif isinstance(e, ProfileServiceError):
2025-07-14 18:36:27 +08:00
return HTTPException(status_code=500, detail=str(e))
else:
return HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
2025-07-08 17:10:15 +08:00
@router.post("/connection-token", response_model=ConnectionTokenResponse)
async def create_connection_token(
request: CreateConnectionTokenRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Creating Pipedream connection token for user: {user_id}, app: {request.app}")
2025-07-08 17:10:15 +08:00
2025-07-14 18:36:27 +08:00
actual_app = _strip_pipedream_prefix(request.app)
2025-07-13 22:29:38 +08:00
2025-07-08 17:10:15 +08:00
try:
2025-07-30 20:27:26 +08:00
from .connection_token_service import ExternalUserId, AppSlug
external_user_id = ExternalUserId(user_id)
app_slug = AppSlug(actual_app) if actual_app else None
result = await connection_token_service.create(external_user_id, app_slug)
2025-07-08 17:10:15 +08:00
return ConnectionTokenResponse(
success=True,
link=result.get("connect_link_url"),
token=result.get("token"),
external_user_id=user_id,
2025-07-14 18:36:27 +08:00
app=actual_app,
2025-07-08 17:10:15 +08:00
expires_at=result.get("expires_at")
)
except Exception as e:
2025-07-14 18:36:27 +08:00
logger.error(f"Failed to create connection token: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-08 17:10:15 +08:00
@router.get("/connections", response_model=ConnectionResponse)
async def get_user_connections(
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Getting connections for user: {user_id}")
2025-07-14 18:36:27 +08:00
2025-07-08 17:10:15 +08:00
try:
2025-07-30 20:27:26 +08:00
from .connection_service import ExternalUserId
external_user_id = ExternalUserId(user_id)
connections = await connection_service.get_connections_for_user(external_user_id)
2025-07-14 18:36:27 +08:00
connection_data = []
for connection in connections:
connection_data.append({
"name": connection.app.name,
2025-07-30 22:03:43 +08:00
"name_slug": connection.app.slug,
2025-07-14 18:36:27 +08:00
"description": connection.app.description,
"category": connection.app.category,
"img_src": connection.app.logo_url,
"auth_type": connection.app.auth_type.value,
"verified": connection.app.is_verified,
"url": connection.app.url,
"tags": connection.app.tags,
"is_active": connection.is_active
})
2025-07-08 17:10:15 +08:00
return ConnectionResponse(
success=True,
2025-07-14 18:36:27 +08:00
connections=connection_data,
count=len(connection_data)
2025-07-08 17:10:15 +08:00
)
except Exception as e:
2025-07-14 18:36:27 +08:00
logger.error(f"Failed to get connections: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-08 17:10:15 +08:00
@router.post("/mcp/discover", response_model=MCPDiscoveryResponse)
async def discover_mcp_servers(
request: MCPDiscoveryRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Discovering MCP servers for user: {user_id}, app: {request.app_slug}")
2025-07-08 17:10:15 +08:00
2025-07-14 18:36:27 +08:00
actual_app_slug = _strip_pipedream_prefix(request.app_slug)
2025-07-13 22:29:38 +08:00
2025-07-08 17:10:15 +08:00
try:
2025-07-30 20:27:26 +08:00
from .mcp_service import ExternalUserId, AppSlug
external_user_id = ExternalUserId(user_id)
app_slug_obj = AppSlug(actual_app_slug) if actual_app_slug else None
servers = await mcp_service.discover_servers_for_user(external_user_id, app_slug_obj)
2025-07-14 18:36:27 +08:00
server_data = []
for server in servers:
tools_data = []
for tool in server.available_tools:
tools_data.append({
"name": tool.name,
"description": tool.description,
"inputSchema": tool.input_schema
})
server_data.append({
2025-07-30 22:03:43 +08:00
"app_slug": server.app_slug,
2025-07-14 18:36:27 +08:00
"app_name": server.app_name,
2025-07-30 22:03:43 +08:00
"server_url": server.server_url,
2025-07-14 18:36:27 +08:00
"project_id": server.project_id,
"environment": server.environment,
2025-07-30 22:03:43 +08:00
"external_user_id": server.external_user_id,
2025-07-14 18:36:27 +08:00
"oauth_app_id": server.oauth_app_id,
"status": server.status.value,
"available_tools": tools_data,
"error": server.error_message
})
2025-07-08 17:10:15 +08:00
return MCPDiscoveryResponse(
success=True,
2025-07-14 18:36:27 +08:00
mcp_servers=server_data,
count=len(server_data)
2025-07-08 17:10:15 +08:00
)
except Exception as e:
2025-07-14 18:36:27 +08:00
logger.error(f"Failed to discover MCP servers: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-08 17:10:15 +08:00
2025-07-09 02:40:58 +08:00
@router.post("/mcp/discover-profile", response_model=MCPDiscoveryResponse)
async def discover_mcp_servers_for_profile(
request: MCPProfileDiscoveryRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Discovering MCP servers for profile: {request.external_user_id}")
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
actual_app_slug = _strip_pipedream_prefix(request.app_slug)
2025-07-13 22:29:38 +08:00
2025-07-09 02:40:58 +08:00
try:
2025-07-30 20:27:26 +08:00
from .mcp_service import ExternalUserId, AppSlug
external_user_id = ExternalUserId(request.external_user_id)
app_slug_obj = AppSlug(actual_app_slug) if actual_app_slug else None
servers = await mcp_service.discover_servers_for_user(external_user_id, app_slug_obj)
2025-07-14 18:36:27 +08:00
server_data = []
for server in servers:
tools_data = []
for tool in server.available_tools:
tools_data.append({
"name": tool.name,
"description": tool.description,
"inputSchema": tool.input_schema
})
server_data.append({
2025-07-30 22:03:43 +08:00
"app_slug": server.app_slug,
2025-07-14 18:36:27 +08:00
"app_name": server.app_name,
2025-07-30 22:03:43 +08:00
"server_url": server.server_url,
2025-07-14 18:36:27 +08:00
"project_id": server.project_id,
"environment": server.environment,
2025-07-30 22:03:43 +08:00
"external_user_id": server.external_user_id,
2025-07-14 18:36:27 +08:00
"oauth_app_id": server.oauth_app_id,
"status": server.status.value,
"available_tools": tools_data,
"error": server.error_message
})
2025-07-09 02:40:58 +08:00
return MCPDiscoveryResponse(
success=True,
2025-07-14 18:36:27 +08:00
mcp_servers=server_data,
count=len(server_data)
2025-07-09 02:40:58 +08:00
)
except Exception as e:
2025-07-14 18:36:27 +08:00
logger.error(f"Failed to discover MCP servers for profile: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
2025-07-08 17:10:15 +08:00
@router.post("/mcp/connect", response_model=MCPConnectionResponse)
async def create_mcp_connection(
request: MCPConnectionRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Creating MCP connection for user: {user_id}, app: {request.app_slug}")
2025-07-13 22:29:38 +08:00
2025-07-14 18:36:27 +08:00
actual_app_slug = _strip_pipedream_prefix(request.app_slug)
2025-07-13 22:29:38 +08:00
2025-07-08 17:10:15 +08:00
try:
2025-07-30 20:27:26 +08:00
from .mcp_service import ExternalUserId, AppSlug
external_user_id = ExternalUserId(user_id)
app_slug_obj = AppSlug(actual_app_slug)
server = await mcp_service.create_connection(
external_user_id,
app_slug_obj,
2025-07-14 18:36:27 +08:00
request.oauth_app_id
2025-07-08 17:10:15 +08:00
)
2025-07-14 18:36:27 +08:00
tools_data = []
for tool in server.available_tools:
tools_data.append({
"name": tool.name,
"description": tool.description,
"inputSchema": tool.input_schema
})
mcp_config = {
2025-07-30 22:03:43 +08:00
"app_slug": server.app_slug,
2025-07-14 18:36:27 +08:00
"app_name": server.app_name,
2025-07-30 22:03:43 +08:00
"server_url": server.server_url,
2025-07-14 18:36:27 +08:00
"project_id": server.project_id,
"environment": server.environment,
2025-07-30 22:03:43 +08:00
"external_user_id": server.external_user_id,
2025-07-14 18:36:27 +08:00
"oauth_app_id": server.oauth_app_id,
"status": server.status.value,
"available_tools": tools_data
}
2025-07-08 17:10:15 +08:00
return MCPConnectionResponse(
success=True,
mcp_config=mcp_config
)
2025-07-08 21:18:49 +08:00
except Exception as e:
2025-07-14 18:36:27 +08:00
logger.error(f"Failed to create MCP connection: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-08 21:18:49 +08:00
@router.get("/apps", response_model=Dict[str, Any])
async def get_pipedream_apps(
2025-07-10 22:44:09 +08:00
after: Optional[str] = Query(None, description="Cursor for pagination"),
2025-07-10 01:57:25 +08:00
q: Optional[str] = Query(None),
2025-07-08 21:18:49 +08:00
category: Optional[str] = Query(None)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Fetching Pipedream apps: query='{q}', category='{category}'")
2025-07-08 21:18:49 +08:00
try:
2025-07-30 20:27:26 +08:00
result = await app_service.search_apps(
2025-07-14 18:36:27 +08:00
query=q,
category=category,
cursor=after
2025-07-10 22:44:09 +08:00
)
2025-07-14 18:36:27 +08:00
apps_data = []
for app in result.get("apps", []):
categories = []
if app.category and app.category != "Other":
categories.append(app.category)
if app.tags:
for tag in app.tags:
if tag and tag not in categories:
categories.append(tag)
if not categories and app.category:
categories.append(app.category)
apps_data.append({
"name": app.name,
2025-07-30 22:22:31 +08:00
"name_slug": app.slug,
2025-07-14 18:36:27 +08:00
"description": app.description,
"category": app.category,
"categories": categories,
"img_src": app.logo_url,
"auth_type": app.auth_type.value,
"verified": app.is_verified,
"url": app.url,
"tags": app.tags,
"featured_weight": app.featured_weight
})
2025-07-10 22:44:09 +08:00
2025-07-14 18:36:27 +08:00
return {
"success": True,
"apps": apps_data,
"page_info": result.get("page_info", {}),
"total_count": result.get("total_count", 0)
}
2025-07-10 22:44:09 +08:00
2025-07-14 18:36:27 +08:00
except Exception as e:
logger.error(f"Failed to fetch Pipedream apps: {str(e)}")
raise _handle_pipedream_exception(e)
@router.get("/apps/popular", response_model=Dict[str, Any])
async def get_popular_pipedream_apps():
2025-08-17 10:10:56 +08:00
logger.debug("Fetching popular Pipedream apps")
2025-07-14 18:36:27 +08:00
try:
2025-07-30 20:27:26 +08:00
apps = await app_service.get_popular_apps(limit=100)
2025-07-14 18:36:27 +08:00
apps_data = []
for app in apps:
categories = []
if app.category and app.category != "Other":
categories.append(app.category)
if app.tags:
for tag in app.tags:
if tag and tag not in categories:
categories.append(tag)
if not categories and app.category:
categories.append(app.category)
apps_data.append({
"name": app.name,
2025-07-30 22:22:31 +08:00
"name_slug": app.slug,
2025-07-14 18:36:27 +08:00
"description": app.description,
"category": app.category,
"categories": categories,
"img_src": app.logo_url,
"auth_type": app.auth_type.value,
"verified": app.is_verified,
"url": app.url,
"tags": app.tags,
"featured_weight": app.featured_weight
})
2025-07-10 22:44:09 +08:00
return {
"success": True,
2025-07-14 18:36:27 +08:00
"apps": apps_data,
"page_info": {
"total_count": len(apps_data),
"count": len(apps_data),
"has_more": False
}
2025-07-10 22:44:09 +08:00
}
2025-07-08 21:18:49 +08:00
except Exception as e:
2025-07-14 18:36:27 +08:00
logger.error(f"Failed to fetch popular Pipedream apps: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
@router.get("/apps/{app_slug}/icon")
async def get_app_icon(app_slug: str):
2025-08-17 10:10:56 +08:00
logger.debug(f"Fetching icon for app: {app_slug}")
2025-07-14 18:36:27 +08:00
try:
2025-07-30 20:27:26 +08:00
app = await app_service.get_app_by_slug(app_slug)
icon_url = app.logo_url if app else None
2025-07-14 18:36:27 +08:00
if icon_url:
return {
"success": True,
"app_slug": app_slug,
"icon_url": icon_url
}
else:
raise HTTPException(
status_code=404,
detail=f"Icon not found for app: {app_slug}"
)
except Exception as e:
logger.error(f"Failed to fetch icon for app {app_slug}: {str(e)}")
raise _handle_pipedream_exception(e)
2025-07-21 18:33:00 +08:00
@router.get("/apps/{app_slug}/tools")
async def get_app_tools(app_slug: str):
2025-08-17 10:10:56 +08:00
logger.debug(f"Getting tools for app: {app_slug}")
2025-07-21 18:33:00 +08:00
url = f"https://remote.mcp.pipedream.net/?app={app_slug}&externalUserId=tools_preview"
payload = {"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 1}
headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
async with client.stream("POST", url, json=payload, headers=headers) as resp:
resp.raise_for_status()
tools = []
async for line in resp.aiter_lines():
if not line or not line.startswith("data:"):
continue
data_str = line[len("data:"):].strip()
try:
data_obj = json.loads(data_str)
tools = data_obj.get("result", {}).get("tools", [])
for tool in tools:
desc = tool.get("description", "") or ""
idx = desc.find("[")
if idx != -1:
tool["description"] = desc[:idx].strip()
break
except json.JSONDecodeError:
logger.warning(f"Failed to parse JSON data: {data_str}")
continue
return {"success": True, "tools": tools}
except httpx.HTTPError as e:
logger.error(f"HTTP error when fetching tools for app {app_slug}: {e}")
raise HTTPException(status_code=502, detail="Bad Gateway")
except Exception as e:
logger.error(f"Unexpected error when fetching tools for app {app_slug}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
2025-07-14 18:36:27 +08:00
2025-07-20 12:49:42 +08:00
2025-07-14 18:36:27 +08:00
@router.post("/profiles", response_model=ProfileResponse)
2025-07-09 02:40:58 +08:00
async def create_credential_profile(
2025-07-14 18:36:27 +08:00
request: ProfileRequest,
2025-07-09 02:40:58 +08:00
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Creating credential profile for user: {user_id}, app: {request.app_slug}")
2025-07-09 02:40:58 +08:00
try:
2025-07-30 20:27:26 +08:00
profile = await profile_service.create_profile(
2025-07-30 22:03:43 +08:00
user_id,
request.profile_name,
request.app_slug,
request.app_name,
2025-07-14 18:36:27 +08:00
description=request.description,
is_default=request.is_default,
oauth_app_id=request.oauth_app_id,
enabled_tools=request.enabled_tools,
external_user_id=request.external_user_id
)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
return ProfileResponse.from_domain(profile)
2025-07-09 02:40:58 +08:00
except Exception as e:
logger.error(f"Failed to create credential profile: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
@router.get("/profiles", response_model=List[ProfileResponse])
2025-07-09 02:40:58 +08:00
async def get_credential_profiles(
app_slug: Optional[str] = Query(None),
is_active: Optional[bool] = Query(None),
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Getting credential profiles for user: {user_id}, app: {app_slug}")
2025-07-14 18:36:27 +08:00
actual_app_slug = _strip_pipedream_prefix(app_slug)
2025-07-09 02:40:58 +08:00
try:
2025-07-30 22:03:43 +08:00
profiles = await profile_service.get_profiles(user_id, actual_app_slug, is_active)
2025-07-14 18:36:27 +08:00
return [ProfileResponse.from_domain(profile) for profile in profiles]
2025-07-09 02:40:58 +08:00
except Exception as e:
logger.error(f"Failed to get credential profiles: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
@router.get("/profiles/{profile_id}", response_model=ProfileResponse)
2025-07-09 02:40:58 +08:00
async def get_credential_profile(
profile_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Getting credential profile: {profile_id} for user: {user_id}")
2025-07-09 02:40:58 +08:00
try:
2025-07-30 22:03:43 +08:00
profile = await profile_service.get_profile(user_id, profile_id)
2025-07-09 02:40:58 +08:00
if not profile:
2025-07-30 20:27:26 +08:00
from .profile_service import ProfileNotFoundError
2025-07-14 18:36:27 +08:00
raise ProfileNotFoundError(profile_id)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
return ProfileResponse.from_domain(profile)
2025-07-09 02:40:58 +08:00
except Exception as e:
logger.error(f"Failed to get credential profile: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
@router.put("/profiles/{profile_id}", response_model=ProfileResponse)
2025-07-09 02:40:58 +08:00
async def update_credential_profile(
profile_id: str,
request: UpdateProfileRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Updating credential profile: {profile_id} for user: {user_id}")
2025-07-09 02:40:58 +08:00
try:
2025-07-30 20:27:26 +08:00
profile = await profile_service.update_profile(
2025-07-30 22:03:43 +08:00
user_id,
profile_id,
2025-07-14 18:36:27 +08:00
profile_name=request.profile_name,
display_name=request.display_name,
is_active=request.is_active,
is_default=request.is_default,
enabled_tools=request.enabled_tools
)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
return ProfileResponse.from_domain(profile)
2025-07-09 02:40:58 +08:00
except Exception as e:
logger.error(f"Failed to update credential profile: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
@router.delete("/profiles/{profile_id}")
async def delete_credential_profile(
profile_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Deleting credential profile: {profile_id} for user: {user_id}")
2025-07-09 02:40:58 +08:00
try:
2025-07-30 22:03:43 +08:00
success = await profile_service.delete_profile(user_id, profile_id)
2025-07-09 02:40:58 +08:00
2025-07-14 18:36:27 +08:00
if not success:
raise ProfileNotFoundError(profile_id)
2025-07-09 02:40:58 +08:00
return {"success": True, "message": "Profile deleted successfully"}
except Exception as e:
logger.error(f"Failed to delete credential profile: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
@router.post("/profiles/{profile_id}/connect")
async def connect_credential_profile(
profile_id: str,
app: Optional[str] = Query(None),
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Connecting credential profile: {profile_id} for user: {user_id}")
2025-07-14 18:36:27 +08:00
actual_app = _strip_pipedream_prefix(app)
2025-07-09 02:40:58 +08:00
try:
2025-07-30 20:27:26 +08:00
from uuid import UUID
from .profile_service import ProfileNotFoundError
from .connection_token_service import ExternalUserId, AppSlug
2025-07-30 22:03:43 +08:00
profile = await profile_service.get_profile(user_id, profile_id)
2025-07-14 18:36:27 +08:00
if not profile:
raise ProfileNotFoundError(profile_id)
2025-07-09 02:40:58 +08:00
2025-07-30 22:03:43 +08:00
external_user_id = ExternalUserId(profile.external_user_id)
app_slug = AppSlug(actual_app or profile.app_slug)
2025-07-30 20:27:26 +08:00
result = await connection_token_service.create(external_user_id, app_slug)
2025-07-14 18:36:27 +08:00
return {
"success": True,
"link": result.get("connect_link_url"),
"token": result.get("token"),
"expires_at": result.get("expires_at"),
"profile_id": profile_id,
2025-07-30 22:03:43 +08:00
"external_user_id": profile.external_user_id,
"app": actual_app or profile.app_slug
2025-07-14 18:36:27 +08:00
}
2025-07-09 02:40:58 +08:00
except Exception as e:
logger.error(f"Failed to connect credential profile: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)
2025-07-09 02:40:58 +08:00
@router.get("/profiles/{profile_id}/connections")
async def get_profile_connections(
profile_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
2025-08-17 10:10:56 +08:00
logger.debug(f"Getting connections for profile: {profile_id}, user: {user_id}")
2025-07-09 02:40:58 +08:00
try:
2025-07-30 20:27:26 +08:00
from uuid import UUID
from .profile_service import ProfileNotFoundError
from .connection_service import ExternalUserId
2025-07-30 22:03:43 +08:00
profile = await profile_service.get_profile(user_id, profile_id)
2025-07-14 18:36:27 +08:00
if not profile:
raise ProfileNotFoundError(profile_id)
2025-07-30 22:03:43 +08:00
external_user_id = ExternalUserId(profile.external_user_id)
2025-07-30 20:27:26 +08:00
connections = await connection_service.get_connections_for_user(external_user_id)
2025-07-14 18:36:27 +08:00
connection_data = []
for connection in connections:
connection_data.append({
"name": connection.app.name,
2025-07-30 22:03:43 +08:00
"name_slug": connection.app.slug,
2025-07-14 18:36:27 +08:00
"description": connection.app.description,
"category": connection.app.category,
"img_src": connection.app.logo_url,
"auth_type": connection.app.auth_type.value,
"verified": connection.app.is_verified,
"url": connection.app.url,
"tags": connection.app.tags,
"is_active": connection.is_active
})
2025-07-09 02:40:58 +08:00
return {
"success": True,
2025-07-14 18:36:27 +08:00
"connections": connection_data,
"count": len(connection_data)
2025-07-09 02:40:58 +08:00
}
except Exception as e:
logger.error(f"Failed to get profile connections: {str(e)}")
2025-07-14 18:36:27 +08:00
raise _handle_pipedream_exception(e)