from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from typing import Optional from datetime import datetime from ..models.api_token import APIToken from ..services.sync_app import SyncApp from ..services.garmin.client import GarminClient from ..services.postgresql_manager import PostgreSQLManager from sqlalchemy.orm import Session from ..utils.config import config import logging import json import garth from garth.auth_tokens import OAuth1Token, OAuth2Token router = APIRouter() logger = logging.getLogger(__name__) class SyncActivityRequest(BaseModel): days_back: int = 30 class SyncResponse(BaseModel): status: str message: str job_id: Optional[str] = None def get_db(): db_manager = PostgreSQLManager(config.DATABASE_URL) with db_manager.get_db_session() as session: yield session def _load_and_verify_garth_session(db: Session): """Helper to load token from DB and verify session with Garmin.""" logger.info("Loading and verifying Garmin session...") token_record = db.query(APIToken).filter_by(token_type='garmin').first() if not (token_record and token_record.garth_oauth1_token and token_record.garth_oauth2_token): raise HTTPException(status_code=401, detail="Garmin token not found.") try: oauth1_dict = json.loads(token_record.garth_oauth1_token) oauth2_dict = json.loads(token_record.garth_oauth2_token) domain = oauth1_dict.get('domain') if domain: garth.configure(domain=domain) garth.client.oauth1_token = OAuth1Token(**oauth1_dict) garth.client.oauth2_token = OAuth2Token(**oauth2_dict) garth.UserProfile.get() logger.info("Garth session verified.") except Exception as e: logger.error(f"Garth session verification failed: {e}", exc_info=True) raise HTTPException(status_code=401, detail=f"Failed to authenticate with Garmin: {e}") @router.post("/sync/activities", response_model=SyncResponse) def sync_activities(request: SyncActivityRequest, db: Session = Depends(get_db)): _load_and_verify_garth_session(db) garmin_client = GarminClient() # The client is now just a thin wrapper sync_app = SyncApp(db_session=db, garmin_client=garmin_client) result = sync_app.sync_activities(days_back=request.days_back) return SyncResponse( status=result.get("status", "completed_with_errors" if result.get("failed", 0) > 0 else "completed"), message=f"Activity sync completed: {result.get('processed', 0)} processed, {result.get('failed', 0)} failed", job_id=f"activity-sync-{datetime.now().strftime('%Y%m%d%H%M%S')}" ) @router.post("/sync/metrics", response_model=SyncResponse) def sync_metrics(db: Session = Depends(get_db)): _load_and_verify_garth_session(db) garmin_client = GarminClient() sync_app = SyncApp(db_session=db, garmin_client=garmin_client) result = sync_app.sync_health_metrics() return SyncResponse( status=result.get("status", "completed_with_errors" if result.get("failed", 0) > 0 else "completed"), message=f"Health metrics sync completed: {result.get('processed', 0)} processed, {result.get('failed', 0)} failed", job_id=f"metrics-sync-{datetime.now().strftime('%Y%m%d%H%M%S')}" )