import logging from pathlib import Path from typing import Optional, List import httpx from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type from ..schemas import GarminCredentials, Token, User, WorkoutPlan from ..config import settings logger = logging.getLogger(__name__) # Define a retry strategy for CentralDB calls CENTRAL_DB_RETRY_STRATEGY = retry( stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10), retry=retry_if_exception_type(httpx.RequestError), # Retry on network errors reraise=True ) class CentralDBService: def __init__(self, base_url: str): self.base_url = base_url @CENTRAL_DB_RETRY_STRATEGY async def get_user_by_email(self, email: str) -> Optional[User]: try: async with httpx.AsyncClient() as client: response = await client.get(f"{self.base_url}/users") response.raise_for_status() users = response.json() for user_data in users: if user_data["email"] == email: return User(**user_data) return None except Exception as e: logger.error(f"Error fetching user from CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def get_user(self, user_id: int) -> Optional[User]: try: async with httpx.AsyncClient() as client: response = await client.get(f"{self.base_url}/users/{user_id}") response.raise_for_status() return User(**response.json()) except Exception as e: logger.error(f"Error fetching user from CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def create_user(self, user_create: dict) -> Optional[User]: try: async with httpx.AsyncClient() as client: response = await client.post(f"{self.base_url}/users", json=user_create) response.raise_for_status() return User(**response.json()) except Exception as e: logger.error(f"Error creating user in CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def get_token(self, user_id: int) -> Optional[Token]: try: async with httpx.AsyncClient() as client: response = await client.get(f"{self.base_url}/tokens/{user_id}") response.raise_for_status() return Token(**response.json()) except Exception as e: logger.error(f"Error fetching token from CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def create_token(self, token_create: dict) -> Optional[Token]: try: async with httpx.AsyncClient() as client: response = await client.post(f"{self.base_url}/tokens/", json=token_create) response.raise_for_status() return Token(**response.json()) except Exception as e: logger.error(f"Error creating token in CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def update_token(self, user_id: int, token_update: dict) -> Optional[Token]: try: async with httpx.AsyncClient() as client: response = await client.put(f"{self.base_url}/tokens/{user_id}", json=token_update) response.raise_for_status() return Token(**response.json()) except Exception as e: logger.error(f"Error updating token in CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def get_workout_by_id(self, workout_id: int) -> Optional[WorkoutPlan]: try: async with httpx.AsyncClient() as client: response = await client.get(f"{self.base_url}/workout_plans/{workout_id}") response.raise_for_status() return WorkoutPlan(**response.json()) except Exception as e: logger.error(f"Error fetching workout from CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def upload_activity_file(self, activity_id: str, file_path: Path) -> bool: """Uploads activity file content to CentralDB.""" try: async with httpx.AsyncClient() as client: with open(file_path, "rb") as f: files = {"file": (file_path.name, f, "application/fit")} # Changed content type user_id = 1 # Assuming single user for now response = await client.post( f"{self.base_url}/activities/{user_id}", # user_id as path parameter files=files, ) response.raise_for_status() logger.info(f"Successfully uploaded activity {activity_id} to CentralDB.") return True except Exception as e: logger.error(f"Error uploading activity {activity_id} to CentralDB: {e}", exc_info=True) return False @CENTRAL_DB_RETRY_STRATEGY async def save_health_metric(self, health_metric_data: dict) -> Optional[dict]: try: async with httpx.AsyncClient() as client: response = await client.post(f"{self.base_url}/health_metrics", json=health_metric_data) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error saving health metric to CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def get_garmin_credentials(self, user_id: int) -> Optional[GarminCredentials]: try: async with httpx.AsyncClient() as client: response = await client.get(f"{self.base_url}/garmin_credentials/{user_id}") response.raise_for_status() return GarminCredentials(**response.json()) except Exception as e: logger.error(f"Error fetching Garmin credentials from CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def create_garmin_credentials(self, user_id: int, credentials_data: dict) -> Optional[GarminCredentials]: try: async with httpx.AsyncClient() as client: response = await client.post(f"{self.base_url}/garmin_credentials/{user_id}", json=credentials_data) response.raise_for_status() return GarminCredentials(**response.json()) except Exception as e: logger.error(f"Error creating Garmin credentials in CentralDB: {e}") return None @CENTRAL_DB_RETRY_STRATEGY async def update_garmin_credentials(self, user_id: int, credentials_data: dict) -> Optional[GarminCredentials]: try: async with httpx.AsyncClient() as client: response = await client.put(f"{self.base_url}/garmin_credentials/{user_id}", json=credentials_data) response.raise_for_status() return GarminCredentials(**response.json()) except Exception as e: logger.error(f"Error updating Garmin credentials in CentralDB: {e}") return None