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