""" Star System Management API routes Handles CRUD operations for star systems (Solar System and exoplanet systems) """ import logging from fastapi import APIRouter, HTTPException, Depends, Query, status from sqlalchemy.ext.asyncio import AsyncSession from typing import Optional from app.database import get_db from app.models.star_system import ( StarSystemCreate, StarSystemUpdate, StarSystemResponse, StarSystemWithBodies, StarSystemListResponse, StarSystemStatistics ) from app.services.star_system_service import star_system_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/star-systems", tags=["star-systems"]) @router.get("", response_model=StarSystemListResponse) async def get_star_systems( skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"), exclude_solar: bool = Query(False, description="Exclude Solar System from results"), search: Optional[str] = Query(None, description="Search by name (English or Chinese)"), db: AsyncSession = Depends(get_db) ): """ Get all star systems with pagination and optional filtering Args: skip: Number of records to skip (for pagination) limit: Maximum number of records to return exclude_solar: If True, exclude Solar System (id=1) from results search: Search keyword to filter by name or host star name db: Database session Returns: List of star systems with total count """ systems = await star_system_service.get_all( db=db, skip=skip, limit=limit, exclude_solar=exclude_solar, search=search ) # Get total count for pagination from sqlalchemy import select, func, or_ from app.models.db.star_system import StarSystem from app.models.db.celestial_body import CelestialBody count_query = select(func.count(StarSystem.id)) if exclude_solar: count_query = count_query.where(StarSystem.id != 1) if search: search_pattern = f"%{search}%" count_query = count_query.where( or_( StarSystem.name.ilike(search_pattern), StarSystem.name_zh.ilike(search_pattern), StarSystem.host_star_name.ilike(search_pattern) ) ) result = await db.execute(count_query) total = result.scalar() # Get star counts for all systems system_ids = [s.id for s in systems] star_counts_query = select( CelestialBody.system_id, func.count(CelestialBody.id).label('star_count') ).where( CelestialBody.system_id.in_(system_ids), CelestialBody.type == 'star' ).group_by(CelestialBody.system_id) star_counts_result = await db.execute(star_counts_query) star_counts_dict = {row.system_id: row.star_count for row in star_counts_result.all()} # Build response with star_count systems_response = [] for system in systems: system_dict = StarSystemResponse.from_orm(system).dict() system_dict['star_count'] = star_counts_dict.get(system.id, 1) # Default to 1 if no stars found systems_response.append(StarSystemResponse(**system_dict)) return StarSystemListResponse( total=total, systems=systems_response ) @router.get("/statistics", response_model=StarSystemStatistics) async def get_statistics(db: AsyncSession = Depends(get_db)): """ Get star system statistics Returns: - Total star systems count - Exoplanet systems count - Total planets count (Solar System + exoplanets) - Nearest star systems (top 10) """ stats = await star_system_service.get_statistics(db) return StarSystemStatistics(**stats) @router.get("/{system_id}", response_model=StarSystemResponse) async def get_star_system( system_id: int, db: AsyncSession = Depends(get_db) ): """ Get a single star system by ID Args: system_id: Star system ID (1 = Solar System, 2+ = Exoplanet systems) """ system = await star_system_service.get_by_id(db, system_id) if not system: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Star system with ID {system_id} not found" ) return StarSystemResponse.from_orm(system) @router.get("/{system_id}/bodies", response_model=StarSystemWithBodies) async def get_star_system_with_bodies( system_id: int, db: AsyncSession = Depends(get_db) ): """ Get a star system with all its celestial bodies Args: system_id: Star system ID Returns: Star system details along with list of all celestial bodies (stars, planets, dwarf planets, satellites, probes, comets, etc.) """ result = await star_system_service.get_with_bodies(db, system_id) if not result: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Star system with ID {system_id} not found" ) # Convert ORM objects to dicts system_dict = StarSystemResponse.from_orm(result["system"]).dict() bodies_list = [ { "id": body.id, "name": body.name, "name_zh": body.name_zh, "type": body.type, "description": body.description, "details": body.details, "is_active": body.is_active, "extra_data": body.extra_data, } for body in result["bodies"] ] return StarSystemWithBodies( **system_dict, bodies=bodies_list, body_count=result["body_count"] ) @router.post("", status_code=status.HTTP_201_CREATED, response_model=StarSystemResponse) async def create_star_system( system_data: StarSystemCreate, db: AsyncSession = Depends(get_db) ): """ Create a new star system Note: This is an admin operation. Use with caution. """ # Check if name already exists existing = await star_system_service.get_by_name(db, system_data.name) if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Star system with name '{system_data.name}' already exists" ) new_system = await star_system_service.create(db, system_data.dict()) return StarSystemResponse.from_orm(new_system) @router.put("/{system_id}", response_model=StarSystemResponse) async def update_star_system( system_id: int, system_data: StarSystemUpdate, db: AsyncSession = Depends(get_db) ): """ Update a star system Args: system_id: Star system ID to update system_data: Fields to update (only non-null fields will be updated) """ # Filter out None values update_data = {k: v for k, v in system_data.dict().items() if v is not None} if not update_data: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="No fields to update" ) updated_system = await star_system_service.update(db, system_id, update_data) if not updated_system: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Star system with ID {system_id} not found" ) return StarSystemResponse.from_orm(updated_system) @router.delete("/{system_id}") async def delete_star_system( system_id: int, db: AsyncSession = Depends(get_db) ): """ Delete a star system and all its celestial bodies WARNING: This will cascade delete all celestial bodies in this system! Cannot delete Solar System (id=1). """ if system_id == 1: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Cannot delete Solar System" ) try: deleted = await star_system_service.delete_system(db, system_id) if not deleted: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Star system with ID {system_id} not found" ) return { "message": f"Star system {system_id} and all its bodies deleted successfully" } except ValueError as e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=str(e) )