cosmo/backend/app/api/celestial_orbit.py

128 lines
4.3 KiB
Python
Raw Normal View History

2025-12-02 13:25:28 +00:00
"""
Orbit Management API routes
Handles precomputed orbital data for celestial bodies
"""
import logging
2025-12-11 08:31:26 +00:00
from fastapi import APIRouter, HTTPException, Depends, Query, BackgroundTasks
2025-12-02 13:25:28 +00:00
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
2025-12-11 08:31:26 +00:00
from app.services.task_service import task_service
from app.services.nasa_worker import generate_orbits_task
2025-12-02 13:25:28 +00:00
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:
2025-12-08 10:55:38 +00:00
# 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)
2025-12-02 13:25:28 +00:00
result = []
2025-12-08 10:55:38 +00:00
for orbit, body in orbits_with_bodies:
2025-12-02 13:25:28 +00:00
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(
2025-12-11 08:31:26 +00:00
background_tasks: BackgroundTasks,
2025-12-02 13:25:28 +00:00
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)
):
"""
2025-12-11 08:31:26 +00:00
Generate orbital data for celestial bodies (Background Task)
2025-12-02 13:25:28 +00:00
2025-12-11 08:31:26 +00:00
This endpoint starts a background task to query NASA Horizons API
and generate complete orbital paths.
2025-12-02 13:25:28 +00:00
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:
2025-12-11 08:31:26 +00:00
- Task ID and status message
2025-12-02 13:25:28 +00:00
"""
2025-12-11 08:31:26 +00:00
logger.info("🌌 Starting orbit generation task...")
2025-12-02 13:25:28 +00:00
try:
2025-12-11 08:31:26 +00:00
# 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)
2025-12-02 13:25:28 +00:00
return {
2025-12-11 08:31:26 +00:00
"message": "Orbit generation task started",
"task_id": task.id
2025-12-02 13:25:28 +00:00
}
except Exception as e:
2025-12-11 08:31:26 +00:00
logger.error(f"Orbit generation start failed: {e}")
2025-12-02 13:25:28 +00:00
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")