cosmo/backend/app/api/routes.py

116 lines
3.5 KiB
Python

"""
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"}