165 lines
5.3 KiB
Python
165 lines
5.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.get("/orbits/{body_id}")
|
|
async def get_orbit_by_id(
|
|
body_id: str,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Get orbit data for a specific celestial body
|
|
|
|
Path parameters:
|
|
- body_id: Celestial body ID
|
|
|
|
Returns:
|
|
- Orbit data with points, num_points, period_days, and color
|
|
"""
|
|
logger.info(f"Fetching orbit for body {body_id}")
|
|
|
|
try:
|
|
orbit = await orbit_service.get_orbit(body_id, db)
|
|
|
|
if not orbit:
|
|
raise HTTPException(status_code=404, detail=f"Orbit not found for body {body_id}")
|
|
|
|
return {
|
|
"body_id": orbit.body_id,
|
|
"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
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Failed to fetch orbit for {body_id}: {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")
|