235 lines
7.7 KiB
Python
235 lines
7.7 KiB
Python
import logging
|
|
from typing import Any, Dict, Optional
|
|
|
|
import requests
|
|
from fastapi import HTTPException
|
|
|
|
from config import Config
|
|
|
|
|
|
class KleerAPIError(Exception):
|
|
"""Custom exception for Kleer API errors."""
|
|
pass
|
|
|
|
|
|
class KleerAPI:
|
|
"""Client for interacting with the Kleer API."""
|
|
|
|
def __init__(self, api_url: Optional[str] = None, company_id: Optional[str] = None):
|
|
"""
|
|
Initialize Kleer API client.
|
|
|
|
Args:
|
|
api_url: Base URL for the Kleer API
|
|
company_id: Company ID for API requests
|
|
|
|
Raises:
|
|
ValueError: If API key is not configured
|
|
"""
|
|
self.api_url = api_url or Config.API_URL
|
|
self.company_id = company_id or Config.get_company_id()
|
|
self.api_key = Config.get_api_key()
|
|
|
|
if not self.api_key:
|
|
raise ValueError("KLEER_API_KEY environment variable is not set")
|
|
|
|
def _get_headers(self) -> Dict[str, str]:
|
|
"""Get headers for API requests."""
|
|
return {
|
|
'X-token': self.api_key,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
def api_request(
|
|
self,
|
|
endpoint: str,
|
|
method: str = 'GET',
|
|
data: Optional[Any] = None
|
|
) -> Optional[Any]:
|
|
"""
|
|
Make a request to the Kleer API.
|
|
|
|
Args:
|
|
endpoint: API endpoint path
|
|
method: HTTP method (GET, POST, PUT)
|
|
data: Request payload for POST/PUT requests
|
|
|
|
Returns:
|
|
JSON response from the API
|
|
|
|
Raises:
|
|
HTTPException: If the request fails
|
|
"""
|
|
url = f"{self.api_url}/{endpoint}"
|
|
headers = self._get_headers()
|
|
|
|
response = None
|
|
try:
|
|
if method == 'GET':
|
|
response = requests.get(url, headers=headers)
|
|
elif method == 'POST':
|
|
response = requests.post(url, headers=headers, json=data)
|
|
elif method == 'PUT':
|
|
response = requests.put(url, headers=headers, json=data)
|
|
else:
|
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except requests.HTTPError as e:
|
|
logging.error(f"HTTP error during API request: {e}")
|
|
raise HTTPException(
|
|
status_code=response.status_code if response else 502,
|
|
detail=f"API request failed: {str(e)}"
|
|
)
|
|
except requests.RequestException as e:
|
|
logging.error(f"API request failed: {e}")
|
|
raise HTTPException(status_code=502, detail=f"API request failed: {str(e)}")
|
|
|
|
def get_events(self, user_id: int, start_date: str, end_date: str) -> Dict[str, Any]:
|
|
"""
|
|
Retrieve events for a user within a date range.
|
|
|
|
Args:
|
|
user_id: User ID
|
|
start_date: Start date in YYYY-MM-DD format
|
|
end_date: End date in YYYY-MM-DD format
|
|
|
|
Returns:
|
|
Event data from API
|
|
"""
|
|
endpoint = (
|
|
f"company/{self.company_id}/event"
|
|
f"?userId={user_id}&startDate={start_date}&endDate={end_date}"
|
|
)
|
|
return self.api_request(endpoint, method='GET') or {}
|
|
|
|
def approve_events(self, user_id: int, start_date: str, end_date: str) -> Dict[str, Any]:
|
|
"""
|
|
Approve events for a user within a date range.
|
|
|
|
Args:
|
|
user_id: User ID
|
|
start_date: Start date in YYYY-MM-DD format
|
|
end_date: End date in YYYY-MM-DD format
|
|
|
|
Returns:
|
|
Approval response from API
|
|
"""
|
|
endpoint = f"company/{self.company_id}/event/approve"
|
|
payload = {
|
|
"user-id": {"id": user_id},
|
|
"start-date": start_date,
|
|
"end-date": end_date
|
|
}
|
|
return self.api_request(endpoint, method='POST', data=payload) or {}
|
|
|
|
def put_event(self, event: Dict[str, Any]) -> Optional[Any]:
|
|
"""
|
|
Submit a time event to the Kleer API.
|
|
|
|
Args:
|
|
event: Time event data
|
|
|
|
Returns:
|
|
API response
|
|
|
|
Raises:
|
|
HTTPException: If the request fails
|
|
"""
|
|
url = f"company/{self.company_id}/event"
|
|
try:
|
|
response = self.api_request(url, method='PUT', data=event)
|
|
logging.info(f"Successfully reported event for date: {event.get('date')}")
|
|
return response
|
|
except HTTPException as e:
|
|
logging.error(
|
|
f"Failed to put event for date {event.get('date')}. "
|
|
f"Status: {e.status_code}, Detail: {e.detail}"
|
|
)
|
|
raise
|
|
except Exception as e:
|
|
logging.error(
|
|
f"Unexpected error while putting event for date {event.get('date')}: {e}"
|
|
)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"An unexpected error occurred: {str(e)}"
|
|
)
|
|
|
|
def get_projects(self, user_id: int) -> Dict[str, Dict[str, Any]]:
|
|
"""
|
|
Retrieve active projects for a user.
|
|
|
|
Args:
|
|
user_id: User ID to filter projects
|
|
|
|
Returns:
|
|
Dictionary mapping project names to project info
|
|
"""
|
|
url = f"company/{self.company_id}/client-project?filter=active"
|
|
data = self.api_request(url, method='GET')
|
|
|
|
if not data:
|
|
logging.warning("No data received from projects API.")
|
|
return {}
|
|
|
|
projects_data = data.get('client-project-readables') if isinstance(data, dict) else data
|
|
if not isinstance(projects_data, list):
|
|
logging.error("Unexpected projects data format.")
|
|
return {}
|
|
|
|
projects = {}
|
|
for project in projects_data:
|
|
if not isinstance(project, dict):
|
|
logging.warning(f"Unexpected project data format: {project}")
|
|
continue
|
|
|
|
project_id = project.get('id')
|
|
project_name = project.get('name')
|
|
activity_id = None
|
|
|
|
users = project.get('users', [])
|
|
for user in users:
|
|
if user.get('user', {}).get('id') != user_id:
|
|
continue
|
|
activities = user.get('activities', [])
|
|
for activity in activities:
|
|
activity_id = activity.get('activity', {})
|
|
if activity_id:
|
|
break
|
|
if activity_id:
|
|
break
|
|
|
|
if not project_id or not project_name:
|
|
logging.info(f"Skipping project with missing ID or name: {project}")
|
|
continue
|
|
|
|
if activity_id:
|
|
projects[project_name] = {"id": project_id, "activity": activity_id}
|
|
else:
|
|
logging.info(
|
|
f"Project '{project_name}' (ID: {project_id}) has no associated activity."
|
|
)
|
|
|
|
logging.info(f"Found {len(projects)} valid projects.")
|
|
return projects
|
|
|
|
def get_user_info(self) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Retrieve user information from the Kleer API.
|
|
|
|
Returns:
|
|
User information dictionary or None if not found
|
|
"""
|
|
url = f"company/{self.company_id}/user"
|
|
data = self.api_request(url, method='GET')
|
|
|
|
if data and isinstance(data, dict):
|
|
logging.info("User info retrieved from Kleer API.")
|
|
return data
|
|
|
|
logging.warning("No user information found or data format is incorrect.")
|
|
return None
|