cosmo/backend/app/api/celestial_orbit.py

128 lines
4.3 KiB
Python

"""
Orbit Management API routes
Handles precomputed orbital data for celestial bodies
"""
import logging
from fastapi import APIRouter, HTTPException, Depends, Query, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional
from app.database import get_db
from app.services.horizons import horizons_service
from app.services.db_service import celestial_body_service
from app.services.orbit_service import orbit_service
from app.services.task_service import task_service
from app.services.nasa_worker import generate_orbits_task
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/celestial", tags=["celestial-orbit"])
@router.get("/orbits")
async def get_orbits(
body_type: Optional[str] = Query(None, description="Filter by body type (planet, dwarf_planet)"),
db: AsyncSession = Depends(get_db)
):
"""
Get all precomputed orbital data
Query parameters:
- body_type: Optional filter by celestial body type (planet, dwarf_planet)
Returns:
- List of orbits with points, colors, and metadata
"""
logger.info(f"Fetching orbits (type filter: {body_type})")
try:
# Use optimized query with JOIN to avoid N+1 problem
orbits_with_bodies = await orbit_service.get_all_orbits_with_bodies(db, body_type=body_type)
result = []
for orbit, body in orbits_with_bodies:
result.append({
"body_id": orbit.body_id,
"body_name": body.name if body else "Unknown",
"body_name_zh": body.name_zh if body else None,
"points": orbit.points,
"num_points": orbit.num_points,
"period_days": orbit.period_days,
"color": orbit.color,
"updated_at": orbit.updated_at.isoformat() if orbit.updated_at else None
})
logger.info(f"✅ Returning {len(result)} orbits")
return {"orbits": result}
except Exception as e:
logger.error(f"Failed to fetch orbits: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/admin/orbits/generate")
async def generate_orbits(
background_tasks: BackgroundTasks,
body_ids: Optional[str] = Query(None, description="Comma-separated body IDs to generate. If empty, generates for all planets and dwarf planets"),
db: AsyncSession = Depends(get_db)
):
"""
Generate orbital data for celestial bodies (Background Task)
This endpoint starts a background task to query NASA Horizons API
and generate complete orbital paths.
Query parameters:
- body_ids: Optional comma-separated list of body IDs (e.g., "399,999")
If not provided, generates orbits for all planets and dwarf planets
Returns:
- Task ID and status message
"""
logger.info("🌌 Starting orbit generation task...")
try:
# Parse body_ids if provided
target_body_ids = [bid.strip() for bid in body_ids.split(",")] if body_ids else None
# Create task record
task_description = f"Generate orbits for {len(target_body_ids) if target_body_ids else 'all'} bodies"
if target_body_ids:
task_description += f": {', '.join(target_body_ids[:3])}..."
task = await task_service.create_task(
db,
task_type="orbit_generation",
description=task_description,
params={"body_ids": target_body_ids},
created_by=None # System or Admin
)
# Add to background tasks
background_tasks.add_task(generate_orbits_task, task.id, target_body_ids)
return {
"message": "Orbit generation task started",
"task_id": task.id
}
except Exception as e:
logger.error(f"Orbit generation start failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/admin/orbits/{body_id}")
async def delete_orbit(
body_id: str,
db: AsyncSession = Depends(get_db)
):
"""Delete orbit data for a specific body"""
logger.info(f"Deleting orbit for body {body_id}")
deleted = await orbit_service.delete_orbit(body_id, db)
if deleted:
return {"message": f"Orbit for {body_id} deleted successfully"}
else:
raise HTTPException(status_code=404, detail="Orbit not found")