from fastapi import APIRouter, Header, Request, HTTPException import logging from services.supabase_service import supabase_service from integrations.jira_service import JiraIntegrationService from typing import Dict, Any from datetime import datetime logger = logging.getLogger(__name__) router = APIRouter(prefix="/webhooks/jira", tags=["Webhooks"]) @router.post("") async def receive_jira_webhook( request: Request, x_atlassian_webhook_identifier: str = Header(None) ): """ Receive webhook events from Jira and sync data using the user's stored credentials """ try: payload = await request.json() logger.info(f"Received Jira webhook: {x_atlassian_webhook_identifier}") # 1. Extract Jira Account ID from payload account_id = None if "user" in payload and "accountId" in payload["user"]: account_id = payload["user"]["accountId"] elif "issue" in payload and payload["issue"].get("fields", {}).get("assignee"): account_id = payload["issue"]["fields"]["assignee"].get("accountId") elif "issue" in payload and payload["issue"].get("fields", {}).get("reporter"): account_id = payload["issue"]["fields"]["reporter"].get("accountId") if not account_id: logger.warning("Could not identify Jira Account ID from webhook payload") logger.warning(f"Payload snippet: {str(payload)[:500]}...") return {"status": "ignored", "reason": "no_account_id_found"} # 2. Find user credentials in Supabase using Account ID user_creds = supabase_service.get_user_credentials_by_account_id(account_id) if not user_creds: logger.warning(f"No Supabase user found with Jira Account ID: {account_id}") # This is expected for users who haven't logged in yet with the new auth flow return {"status": "ignored", "reason": "user_not_linked"} firebase_id = user_creds.get("firebase_id") jira_token = user_creds.get("jira_api_token") jira_url = user_creds.get("jira_server_url") jira_email = user_creds.get("jira_email") if not jira_token or not jira_url: logger.warning(f"User {firebase_id} found but missing Jira credentials") return {"status": "ignored", "reason": "credentials_missing"} # 3. Initialize Jira Service with USER'S credentials try: jira_service = JiraIntegrationService( jira_email=jira_email, jira_api_token=jira_token, jira_server_url=jira_url ) logger.info(f"Initialized Jira service for user {jira_email} ({account_id})") # 4. Fetch all data full_data = { "webhook_event": payload, "synced_at": datetime.utcnow().isoformat(), "assigned_issues": [], "projects_data": [], # Renamed to store rich data "boards": [] } # A. Fetch Assigned Issues (Just for quick reference) assigned_issues = jira_service.get_issues_assigned_to_user(account_id) full_data["assigned_issues"] = [issue.model_dump(mode='json') for issue in assigned_issues] # B. Fetch All Projects & Their Contents (Spaces) projects = jira_service.get_projects() logger.info(f"Found {len(projects)} projects for user. Fetching details...") for project in projects: # 1. Fetch All Issues in Project (Limit 50 to avoid timeout for now, can increase) # We need a method to get issues by project key try: project_issues = jira_service.get_issues_by_project(project.project_key, max_results=50) full_data["projects_data"].append({ "project_info": project.model_dump(mode='json'), "issues": [issue.model_dump(mode='json') for issue in project_issues] }) except Exception as e: logger.error(f"Failed to fetch issues for project {project.project_key}: {str(e)}") # Add project without issues in worst case full_data["projects_data"].append({ "project_info": project.model_dump(mode='json'), "issues": [] }) # C. Fetch All Boards & Sprints boards = jira_service.get_boards() enhanced_boards = [] logger.info(f"Found {len(boards)} boards. Fetching sprints...") for board in boards: board_data = board.copy() board_id = board.get('id') # Check Board Type board_type = board.get('type') # A. Scrum Boards: Fetch Sprints if board_type == 'scrum': try: sprints = jira_service.get_sprints(board_id) board_data['sprints'] = [sprint.model_dump(mode='json') for sprint in sprints] except Exception as e: logger.warning(f"Could not fetch sprints for Scrum board {board_id}: {str(e)}") board_data['sprints'] = [] # B. Kanban Boards: Fetch Configuration (Columns & WIP) elif board_type == 'kanban': try: config = jira_service.get_board_configuration(board_id) board_data['configuration'] = config.model_dump(mode='json') except Exception as e: logger.warning(f"Could not fetch config for Kanban board {board_id}: {str(e)}") board_data['configuration'] = {} # C. Unknown/Other else: logger.info(f"Skipping detailed fetch for board type '{board_type}' (ID: {board_id})") enhanced_boards.append(board_data) full_data["boards"] = enhanced_boards logger.info(f"Synced for user {account_id}: Assigned={len(assigned_issues)}, Projects={len(full_data['projects_data'])}, Boards={len(enhanced_boards)}") # 5. Store data success = supabase_service.upsert_jira_data(firebase_id, full_data) if success: return {"status": "processed", "user": firebase_id} else: raise HTTPException(status_code=500, detail="Failed to store data") except Exception as e: logger.error(f"Error syncing data for user {account_id}: {str(e)}") # Return 200 to acknowledge webhook but log error return {"status": "error", "detail": str(e)} except Exception as e: logger.error(f"Error processing webhook: {str(e)}") raise HTTPException(status_code=500, detail=str(e))