""" API routes for celestial data """ from datetime import datetime from fastapi import APIRouter, HTTPException, Query from typing import Optional import logging from app.models.celestial import ( CelestialDataResponse, BodyInfo, CELESTIAL_BODIES, ) from app.services.horizons import horizons_service from app.services.cache import cache_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/celestial", tags=["celestial"]) @router.get("/positions", response_model=CelestialDataResponse) async def get_celestial_positions( start_time: Optional[str] = Query( None, description="Start time in ISO 8601 format (e.g., 2025-01-01T00:00:00Z)", ), end_time: Optional[str] = Query( None, description="End time in ISO 8601 format", ), step: str = Query( "1d", description="Time step (e.g., '1d' for 1 day, '12h' for 12 hours)", ), ): """ Get positions of all celestial bodies for a time range If only start_time is provided, returns a single snapshot. If both start_time and end_time are provided, returns positions at intervals defined by step. """ try: # Parse time strings start_dt = None if start_time is None else datetime.fromisoformat(start_time.replace("Z", "+00:00")) end_dt = None if end_time is None else datetime.fromisoformat(end_time.replace("Z", "+00:00")) # Check cache first cached_data = cache_service.get(start_dt, end_dt, step) if cached_data is not None: return CelestialDataResponse(bodies=cached_data) # Query Horizons logger.info(f"Fetching celestial data from Horizons: start={start_dt}, end={end_dt}, step={step}") bodies = horizons_service.get_all_bodies(start_dt, end_dt, step) # Cache the result cache_service.set(bodies, start_dt, end_dt, step) return CelestialDataResponse(bodies=bodies) except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid time format: {str(e)}") except Exception as e: logger.error(f"Error fetching celestial positions: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to fetch data: {str(e)}") @router.get("/info/{body_id}", response_model=BodyInfo) async def get_body_info(body_id: str): """ Get detailed information about a specific celestial body Args: body_id: JPL Horizons ID (e.g., '-31' for Voyager 1, '399' for Earth) """ if body_id not in CELESTIAL_BODIES: raise HTTPException(status_code=404, detail=f"Body {body_id} not found") info = CELESTIAL_BODIES[body_id] return BodyInfo( id=body_id, name=info["name"], type=info["type"], description=info["description"], launch_date=info.get("launch_date"), status=info.get("status"), ) @router.get("/list") async def list_bodies(): """ Get a list of all available celestial bodies """ bodies_list = [] for body_id, info in CELESTIAL_BODIES.items(): bodies_list.append( { "id": body_id, "name": info["name"], "type": info["type"], "description": info["description"], } ) return {"bodies": bodies_list} @router.post("/cache/clear") async def clear_cache(): """ Clear the data cache (admin endpoint) """ cache_service.clear() return {"message": "Cache cleared successfully"}