Spaces:
Sleeping
Sleeping
| 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"]) | |
| 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)) | |