File size: 7,280 Bytes
6f69304
 
 
9206cfd
6f69304
099ebb2
6f69304
 
 
 
 
 
 
 
 
 
 
4073163
6f69304
 
82b7c1e
6f69304
 
4073163
099ebb2
 
 
 
 
 
 
9206cfd
4073163
 
 
 
9206cfd
4073163
 
6f69304
4073163
 
 
 
6f69304
4073163
 
 
89dff06
099ebb2
4073163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fea3cfe
4073163
 
 
fea3cfe
 
 
4073163
fea3cfe
4073163
fea3cfe
4073163
fea3cfe
 
 
 
6b4df33
fea3cfe
 
 
 
 
 
6b4df33
fea3cfe
 
 
 
 
 
3ceded0
4073163
3ceded0
4073163
3ceded0
 
 
 
 
 
25b1d7a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ceded0
 
 
 
 
4073163
 
 
 
 
 
 
 
099ebb2
4073163
 
 
 
6f69304
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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))