Files
TimeTracker/app/integrations/base.py
Dries Peeters 95a35d2cd0 fix: Complete integration implementations and improve error handling
HIGH PRIORITY FIXES:
- GitHub: Fix webhook signature verification to use raw request body bytes
  * GitHub signs the raw body, not parsed JSON, so signature verification was failing
  * Added security checks to reject webhooks when secret configured but signature missing
  * Updated route handler to pass raw body for proper signature verification

- QuickBooks: Complete customer/account mapping implementation
  * Implemented automatic customer lookup by DisplayName in QuickBooks
  * Implemented automatic item lookup by Name for invoice items
  * Implemented automatic expense account discovery with fallback
  * Auto-saves discovered mappings for future use
  * Added validation to ensure required mappings exist before creating invoices
  * Improved API request handling with proper error messages and timeout handling

IMPROVEMENTS:
- CalDAV: Enhance bidirectional sync functionality
  * Improved update handling to use existing event hrefs correctly
  * Better error handling for HTTP errors (404, etc.)
  * Enhanced event creation vs update logic

- Integrations: Add comprehensive error handling across all integrations
  * GitHub: Network errors, authentication failures, database errors, timeout handling
  * QuickBooks: API errors, validation errors, timeout handling, connection errors
  * CalDAV: HTTP errors, connection errors, timeout handling
  * Jira & Slack: Improved error handling in webhook handlers
  * All integrations now properly handle timeouts, connection errors, auth failures
  * Detailed error messages and appropriate logging levels
  * Proper database transaction rollback on errors

TECHNICAL CHANGES:
- Updated BaseConnector.handle_webhook() to accept optional raw_body parameter
- Updated all webhook handlers (GitHub, Jira, Slack) for consistency
- Improved QuickBooks API request method with better error handling
- Enhanced CalDAV client create_or_update_event() to handle existing hrefs
2025-12-29 12:40:27 +01:00

158 lines
4.3 KiB
Python

"""
Base connector interface for integrations.
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, List
from datetime import datetime
class BaseConnector(ABC):
"""
Base class for all integration connectors.
All connectors must implement these methods to provide
a consistent interface for integration management.
"""
def __init__(self, integration, credentials):
"""
Initialize connector with integration and credentials.
Args:
integration: Integration model instance
credentials: IntegrationCredential model instance
"""
self.integration = integration
self.credentials = credentials
@property
@abstractmethod
def provider_name(self) -> str:
"""Return the provider name (e.g., 'jira', 'slack', 'github')."""
pass
@property
@abstractmethod
def display_name(self) -> str:
"""Return the display name for the provider."""
pass
@abstractmethod
def get_authorization_url(self, redirect_uri: str, state: str = None) -> str:
"""
Get OAuth authorization URL.
Args:
redirect_uri: OAuth callback URL
state: Optional state parameter for CSRF protection
Returns:
Authorization URL
"""
pass
@abstractmethod
def exchange_code_for_tokens(self, code: str, redirect_uri: str) -> Dict[str, Any]:
"""
Exchange authorization code for access tokens.
Args:
code: Authorization code from OAuth callback
redirect_uri: OAuth callback URL
Returns:
Dict with access_token, refresh_token, expires_at, etc.
"""
pass
@abstractmethod
def refresh_access_token(self) -> Dict[str, Any]:
"""
Refresh access token using refresh token.
Returns:
Dict with new access_token, expires_at, etc.
"""
pass
@abstractmethod
def test_connection(self) -> Dict[str, Any]:
"""
Test the connection to the service.
Returns:
Dict with 'success' (bool) and 'message' (str)
"""
pass
def get_access_token(self) -> Optional[str]:
"""
Get current access token, refreshing if needed.
Returns:
Access token string or None
"""
if not self.credentials:
return None
# Check if token needs refresh
if self.credentials.needs_refresh():
try:
new_tokens = self.refresh_access_token()
if new_tokens.get("access_token"):
return new_tokens["access_token"]
except Exception:
pass
return self.credentials.access_token if self.credentials else None
def sync_data(self, sync_type: str = "full") -> Dict[str, Any]:
"""
Sync data from the integrated service.
Args:
sync_type: Type of sync ('full', 'incremental', etc.)
Returns:
Dict with sync results
"""
# Default implementation - override in subclasses
return {"success": False, "message": "Sync not implemented for this connector"}
def handle_webhook(self, payload: Dict[str, Any], headers: Dict[str, str], raw_body: Optional[bytes] = None) -> Dict[str, Any]:
"""
Handle incoming webhook from the service.
Args:
payload: Webhook payload (parsed JSON/dict)
headers: Request headers
raw_body: Raw request body bytes (for signature verification)
Returns:
Dict with processing results
"""
# Default implementation - override in subclasses
return {"success": False, "message": "Webhook handling not implemented for this connector"}
def get_config_schema(self) -> Dict[str, Any]:
"""
Get configuration schema for this connector.
Returns:
Dict describing configuration fields
"""
return {"fields": [], "required": []}
def validate_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Validate configuration.
Args:
config: Configuration dict to validate
Returns:
Dict with 'valid' (bool) and 'errors' (list)
"""
return {"valid": True, "errors": []}