techprotrade's picture
Deploy ATOM FastAPI command center runtime (part 4)
4b9d59b verified
Raw
History Blame Contribute Delete
13.6 kB
"""
Trello Integration Adapter
Provides OAuth-based integration with Trello for board and card management.
"""
import logging
import os
import httpx
from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
from urllib.parse import urlencode
logger = logging.getLogger(__name__)
class TrelloAdapter:
"""
Adapter for Trello OAuth integration.
Supports:
- OAuth 1.0a authentication
- Board and card management
- List and label operations
- Member and team access
"""
def __init__(self, db, workspace_id: str):
self.db = db
self.workspace_id = workspace_id
self.service_name = "trello"
self.base_url = "https://api.trello.com/1"
# OAuth credentials from environment
self.api_key = os.getenv("TRELLO_API_KEY")
self.api_secret = os.getenv("TRELLO_API_SECRET")
self.oauth_token = os.getenv("TRELLO_OAUTH_TOKEN")
self.oauth_token_secret = os.getenv("TRELLO_OAUTH_TOKEN_SECRET")
self.redirect_uri = os.getenv("TRELLO_REDIRECT_URI")
# Token storage
self._access_token: Optional[str] = None
self._token_secret: Optional[str] = None
self._token_expires_at: Optional[datetime] = None
async def get_oauth_url(self) -> str:
"""
Generate Trello OAuth authorization URL.
Returns:
Authorization URL to redirect user to Trello OAuth consent screen
"""
if not self.api_key:
raise ValueError("TRELLO_API_KEY not configured")
# Trello OAuth endpoint
auth_url = "https://trello.com/1/OuthorizeRequestToken"
# Build authorization URL with API key
params = {
"name": "ATOM SaaS Integration",
"expiration": "never",
"scope": "read,write",
"response_type": "token",
"key": self.api_key,
"return_url": self.redirect_uri
}
auth_url_with_params = f"{auth_url}?{urlencode(params)}"
logger.info(f"Generated Trello OAuth URL for workspace {self.workspace_id}")
return auth_url_with_params
async def exchange_code_for_token(self, token: str, verifier: str = None) -> Dict[str, Any]:
"""
Exchange OAuth authorization token for access token.
Args:
token: OAuth token from callback
verifier: OAuth verifier (for OAuth 1.0a flow)
Returns:
Token response with oauth_token and oauth_token_secret
"""
if not self.api_key or not self.api_secret:
raise ValueError("Trello OAuth credentials not configured")
# For Trello, the token from the callback is often the final access token
# If using OAuth 1.0a flow, we need to exchange it
self._access_token = token
if verifier:
self._token_secret = verifier
token_data = {
"oauth_token": token,
"oauth_token_secret": verifier or "token"
}
logger.info(f"Successfully obtained Trello access token for workspace {self.workspace_id}")
return token_data
async def test_connection(self) -> bool:
"""
Test the Trello API connection.
Returns:
True if connection successful, False otherwise
"""
if not self._access_token or not self.api_key:
return False
try:
async with httpx.AsyncClient() as client:
# Test by getting current member info
response = await client.get(
f"{self.base_url}/members/me",
params={
"key": self.api_key,
"token": self._access_token
}
)
response.raise_for_status()
logger.info(f"Trello connection test successful for workspace {self.workspace_id}")
return True
except Exception as e:
logger.error(f"Trello connection test failed: {e}")
return False
async def search_cards(self, query: str, board_id: str = None, limit: int = 10) -> List[Dict[str, Any]]:
"""
Search Trello cards by name or description.
Args:
query: Search query string
board_id: Optional board ID to limit search
limit: Maximum number of results
Returns:
List of card objects
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
async with httpx.AsyncClient() as client:
params = {
"query": query,
"modelTypes": "cards",
"cards_limit": limit,
"card_fields": "name,desc,idList,idBoard,closed,labels",
"key": self.api_key,
"token": self._access_token
}
if board_id:
params["idBoards"] = board_id
response = await client.get(
f"{self.base_url}/search",
params=params
)
response.raise_for_status()
data = response.json()
cards = data.get("cards", [])
logger.info(f"Trello search returned {len(cards)} cards for workspace {self.workspace_id}")
return cards
except Exception as e:
logger.error(f"Trello card search failed: {e}")
raise
async def get_card(self, card_id: str) -> Dict[str, Any]:
"""
Retrieve a specific Trello card by ID.
Args:
card_id: Trello card ID
Returns:
Card details with all fields
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/cards/{card_id}",
params={
"key": self.api_key,
"token": self._access_token,
"fields": "name,desc,closed,due",
"members": "true",
"labels": "true",
"checklists": "true"
}
)
response.raise_for_status()
card = response.json()
logger.info(f"Retrieved Trello card {card_id} for workspace {self.workspace_id}")
return card
except Exception as e:
logger.error(f"Failed to retrieve Trello card {card_id}: {e}")
raise
async def create_card(self, list_id: str, name: str, description: str = None,
due: str = None, labels: List[str] = None) -> Dict[str, Any]:
"""
Create a new Trello card.
Args:
list_id: List ID to add card to
name: Card name (required)
description: Card description
due: Due date (ISO 8601 format)
labels: List of label colors (green, yellow, orange, red, purple, blue)
Returns:
Created card object with ID
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
# Build card data
card_data = {
"idList": list_id,
"name": name,
}
if description:
card_data["desc"] = description
if due:
card_data["due"] = due
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/cards",
params={
"key": self.api_key,
"token": self._access_token,
**card_data
}
)
response.raise_for_status()
card = response.json()
# Add labels if provided
if labels and card.get("id"):
for label in labels:
try:
await client.post(
f"{self.base_url}/cards/{card['id']}/labels",
params={
"key": self.api_key,
"token": self._access_token,
"color": label
}
)
except:
pass # Ignore label errors
logger.info(f"Created Trello card {card.get('id')} for workspace {self.workspace_id}")
return card
except Exception as e:
logger.error(f"Failed to create Trello card: {e}")
raise
async def update_card(self, card_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
"""
Update a Trello card.
Args:
card_id: Card ID to update
updates: Dictionary of fields to update
Returns:
Updated card object
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
async with httpx.AsyncClient() as client:
response = await client.put(
f"{self.base_url}/cards/{card_id}",
params={
"key": self.api_key,
"token": self._access_token,
**updates
}
)
response.raise_for_status()
card = response.json()
logger.info(f"Updated Trello card {card_id} in workspace {self.workspace_id}")
return card
except Exception as e:
logger.error(f"Failed to update Trello card {card_id}: {e}")
raise
async def get_boards(self) -> List[Dict[str, Any]]:
"""
Retrieve all Trello boards for the authenticated member.
Returns:
List of board objects
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/members/me/boards",
params={
"key": self.api_key,
"token": self._access_token,
"fields": "name,closed,pinned",
"lists": "open"
}
)
response.raise_for_status()
boards = response.json()
logger.info(f"Retrieved {len(boards)} Trello boards for workspace {self.workspace_id}")
return boards
except Exception as e:
logger.error(f"Failed to retrieve Trello boards: {e}")
raise
async def add_comment(self, card_id: str, text: str) -> Dict[str, Any]:
"""
Add a comment to a Trello card.
Args:
card_id: Card ID
text: Comment text
Returns:
Created action (comment) object
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/cards/{card_id}/actions/comments",
params={
"key": self.api_key,
"token": self._access_token,
"text": text
}
)
response.raise_for_status()
action = response.json()
logger.info(f"Added comment to Trello card {card_id} in workspace {self.workspace_id}")
return action
except Exception as e:
logger.error(f"Failed to add comment to Trello card {card_id}: {e}")
raise
async def get_lists(self, board_id: str) -> List[Dict[str, Any]]:
"""
Retrieve all lists on a Trello board.
Args:
board_id: Board ID
Returns:
List of list objects
"""
if not self._access_token or not self.api_key:
raise ValueError("Trello API credentials not configured")
try:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/boards/{board_id}/lists",
params={
"key": self.api_key,
"token": self._access_token,
"fields": "name,closed,pos"
}
)
response.raise_for_status()
lists = response.json()
logger.info(f"Retrieved {len(lists)} Trello lists for board {board_id}")
return lists
except Exception as e:
logger.error(f"Failed to retrieve Trello lists for board {board_id}: {e}")
raise