220 lines
6.8 KiB
Python
220 lines
6.8 KiB
Python
"""
|
|
Celestial Body Management API routes
|
|
Handles CRUD operations for celestial bodies (planets, dwarf planets, satellites, probes, etc.)
|
|
"""
|
|
import logging
|
|
from fastapi import APIRouter, HTTPException, Depends, Query, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from pydantic import BaseModel
|
|
from typing import Optional, Dict, Any
|
|
|
|
from app.database import get_db
|
|
from app.models.celestial import BodyInfo
|
|
from app.services.horizons import horizons_service
|
|
from app.services.db_service import celestial_body_service, resource_service
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/celestial", tags=["celestial-body"])
|
|
|
|
|
|
# Pydantic models for CRUD
|
|
class CelestialBodyCreate(BaseModel):
|
|
id: str
|
|
name: str
|
|
name_zh: Optional[str] = None
|
|
type: str
|
|
description: Optional[str] = None
|
|
is_active: bool = True
|
|
extra_data: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class CelestialBodyUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
name_zh: Optional[str] = None
|
|
type: Optional[str] = None
|
|
description: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
extra_data: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
@router.post("", status_code=status.HTTP_201_CREATED)
|
|
async def create_celestial_body(
|
|
body_data: CelestialBodyCreate,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Create a new celestial body"""
|
|
# Check if exists
|
|
existing = await celestial_body_service.get_body_by_id(body_data.id, db)
|
|
if existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Body with ID {body_data.id} already exists"
|
|
)
|
|
|
|
new_body = await celestial_body_service.create_body(body_data.dict(), db)
|
|
return new_body
|
|
|
|
|
|
@router.get("/search")
|
|
async def search_celestial_body(
|
|
name: str = Query(..., description="Body name or ID to search in NASA Horizons")
|
|
):
|
|
"""
|
|
Search for a celestial body in NASA Horizons database by name or ID
|
|
|
|
Returns body information if found, including suggested ID and full name
|
|
"""
|
|
logger.info(f"Searching for celestial body: {name}")
|
|
|
|
try:
|
|
result = await horizons_service.search_body_by_name(name)
|
|
|
|
if result["success"]:
|
|
logger.info(f"Found body: {result['full_name']}")
|
|
return {
|
|
"success": True,
|
|
"data": {
|
|
"id": result["id"],
|
|
"name": result["name"],
|
|
"full_name": result["full_name"],
|
|
}
|
|
}
|
|
else:
|
|
logger.warning(f"Search failed: {result['error']}")
|
|
return {
|
|
"success": False,
|
|
"error": result["error"]
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Search error: {e}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"Search failed: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/{body_id}/nasa-data")
|
|
async def get_celestial_nasa_data(
|
|
body_id: str,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Get raw text data from NASA Horizons for a celestial body
|
|
(Hacker terminal style output)
|
|
"""
|
|
# Check if body exists
|
|
body = await celestial_body_service.get_body_by_id(body_id, db)
|
|
if not body:
|
|
raise HTTPException(status_code=404, detail="Celestial body not found")
|
|
|
|
try:
|
|
# Fetch raw text from Horizons using the body_id
|
|
# Note: body.id corresponds to JPL Horizons ID
|
|
raw_text = await horizons_service.get_object_data_raw(body.id)
|
|
return {"id": body.id, "name": body.name, "raw_data": raw_text}
|
|
except Exception as e:
|
|
logger.error(f"Failed to fetch raw data for {body_id}: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Failed to fetch NASA data: {str(e)}")
|
|
|
|
|
|
@router.put("/{body_id}")
|
|
async def update_celestial_body(
|
|
body_id: str,
|
|
body_data: CelestialBodyUpdate,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Update a celestial body"""
|
|
# Filter out None values
|
|
update_data = {k: v for k, v in body_data.dict().items() if v is not None}
|
|
|
|
updated = await celestial_body_service.update_body(body_id, update_data, db)
|
|
if not updated:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Body {body_id} not found"
|
|
)
|
|
return updated
|
|
|
|
|
|
@router.delete("/{body_id}")
|
|
async def delete_celestial_body(
|
|
body_id: str,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""Delete a celestial body"""
|
|
deleted = await celestial_body_service.delete_body(body_id, db)
|
|
if not deleted:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"Body {body_id} not found"
|
|
)
|
|
return {"message": "Body deleted successfully"}
|
|
|
|
|
|
@router.get("/info/{body_id}", response_model=BodyInfo)
|
|
async def get_body_info(body_id: str, db: AsyncSession = Depends(get_db)):
|
|
"""
|
|
Get detailed information about a specific celestial body
|
|
|
|
Args:
|
|
body_id: JPL Horizons ID (e.g., '-31' for Voyager 1, '399' for Earth)
|
|
"""
|
|
body = await celestial_body_service.get_body_by_id(body_id, db)
|
|
if not body:
|
|
raise HTTPException(status_code=404, detail=f"Body {body_id} not found")
|
|
|
|
# Extract extra_data fields
|
|
extra_data = body.extra_data or {}
|
|
|
|
return BodyInfo(
|
|
id=body.id,
|
|
name=body.name,
|
|
type=body.type,
|
|
description=body.description,
|
|
launch_date=extra_data.get("launch_date"),
|
|
status=extra_data.get("status"),
|
|
)
|
|
|
|
|
|
@router.get("/list")
|
|
async def list_bodies(
|
|
body_type: Optional[str] = Query(None, description="Filter by body type"),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
Get a list of all available celestial bodies
|
|
"""
|
|
bodies = await celestial_body_service.get_all_bodies(db, body_type)
|
|
|
|
bodies_list = []
|
|
for body in bodies:
|
|
# Get resources for this body
|
|
resources = await resource_service.get_resources_by_body(body.id, None, db)
|
|
|
|
# Group resources by type
|
|
resources_by_type = {}
|
|
for resource in resources:
|
|
if resource.resource_type not in resources_by_type:
|
|
resources_by_type[resource.resource_type] = []
|
|
resources_by_type[resource.resource_type].append({
|
|
"id": resource.id,
|
|
"file_path": resource.file_path,
|
|
"file_size": resource.file_size,
|
|
"mime_type": resource.mime_type,
|
|
})
|
|
|
|
bodies_list.append(
|
|
{
|
|
"id": body.id,
|
|
"name": body.name,
|
|
"name_zh": body.name_zh,
|
|
"type": body.type,
|
|
"description": body.description,
|
|
"is_active": body.is_active,
|
|
"resources": resources_by_type,
|
|
"has_resources": len(resources) > 0,
|
|
}
|
|
)
|
|
return {"bodies": bodies_list}
|