first commit
This commit is contained in:
+234
@@ -0,0 +1,234 @@
|
||||
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
|
||||
Reference in New Issue
Block a user