""" System Settings API Routes """ from fastapi import APIRouter, HTTPException, Query, Depends, status from sqlalchemy.ext.asyncio import AsyncSession from typing import Optional, Dict, Any, List import logging from pydantic import BaseModel from app.services.system_settings_service import system_settings_service from app.services.redis_cache import redis_cache from app.services.cache import cache_service from app.database import get_db logger = logging.getLogger(__name__) router = APIRouter(prefix="/system", tags=["system"]) # Pydantic models class SettingCreate(BaseModel): key: str value: Any value_type: str = "string" category: str = "general" label: str description: Optional[str] = None is_public: bool = False class SettingUpdate(BaseModel): value: Optional[Any] = None value_type: Optional[str] = None category: Optional[str] = None label: Optional[str] = None description: Optional[str] = None is_public: Optional[bool] = None # ============================================================ # System Settings CRUD APIs # ============================================================ @router.get("/settings") async def list_settings( category: Optional[str] = Query(None, description="Filter by category"), is_public: Optional[bool] = Query(None, description="Filter by public status"), db: AsyncSession = Depends(get_db) ): """ Get all system settings Query parameters: - category: Optional filter by category (e.g., 'visualization', 'cache', 'ui') - is_public: Optional filter by public status (true for frontend-accessible settings) """ settings = await system_settings_service.get_all_settings(db, category, is_public) result = [] for setting in settings: # Parse value based on type parsed_value = await system_settings_service.get_setting_value(setting.key, db) result.append({ "id": setting.id, "key": setting.key, "value": parsed_value, "raw_value": setting.value, "value_type": setting.value_type, "category": setting.category, "label": setting.label, "description": setting.description, "is_public": setting.is_public, "created_at": setting.created_at.isoformat() if setting.created_at else None, "updated_at": setting.updated_at.isoformat() if setting.updated_at else None, }) return {"settings": result} @router.get("/settings/{key}") async def get_setting( key: str, db: AsyncSession = Depends(get_db) ): """Get a single setting by key""" setting = await system_settings_service.get_setting(key, db) if not setting: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Setting '{key}' not found" ) parsed_value = await system_settings_service.get_setting_value(key, db) return { "id": setting.id, "key": setting.key, "value": parsed_value, "raw_value": setting.value, "value_type": setting.value_type, "category": setting.category, "label": setting.label, "description": setting.description, "is_public": setting.is_public, "created_at": setting.created_at.isoformat() if setting.created_at else None, "updated_at": setting.updated_at.isoformat() if setting.updated_at else None, } @router.post("/settings", status_code=status.HTTP_201_CREATED) async def create_setting( data: SettingCreate, db: AsyncSession = Depends(get_db) ): """Create a new system setting""" # Check if setting already exists existing = await system_settings_service.get_setting(data.key, db) if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Setting '{data.key}' already exists" ) new_setting = await system_settings_service.create_setting(data.dict(), db) await db.commit() parsed_value = await system_settings_service.get_setting_value(data.key, db) return { "id": new_setting.id, "key": new_setting.key, "value": parsed_value, "value_type": new_setting.value_type, "category": new_setting.category, "label": new_setting.label, "description": new_setting.description, "is_public": new_setting.is_public, } @router.put("/settings/{key}") async def update_setting( key: str, data: SettingUpdate, db: AsyncSession = Depends(get_db) ): """Update a system setting""" update_data = {k: v for k, v in data.dict().items() if v is not None} updated = await system_settings_service.update_setting(key, update_data, db) if not updated: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Setting '{key}' not found" ) await db.commit() parsed_value = await system_settings_service.get_setting_value(key, db) return { "id": updated.id, "key": updated.key, "value": parsed_value, "value_type": updated.value_type, "category": updated.category, "label": updated.label, "description": updated.description, "is_public": updated.is_public, } @router.delete("/settings/{key}") async def delete_setting( key: str, db: AsyncSession = Depends(get_db) ): """Delete a system setting""" deleted = await system_settings_service.delete_setting(key, db) if not deleted: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Setting '{key}' not found" ) await db.commit() return {"message": f"Setting '{key}' deleted successfully"} # ============================================================ # Cache Management APIs # ============================================================ @router.post("/cache/clear") async def clear_all_caches(): """ Clear all caches (memory + Redis) This is a critical operation for platform management. It clears: - Memory cache (in-process) - Redis cache (all positions and NASA data) """ logger.info("๐Ÿงน Starting cache clear operation...") # Clear memory cache cache_service.clear() logger.info("โœ“ Memory cache cleared") # Clear Redis cache positions_cleared = await redis_cache.clear_pattern("positions:*") nasa_cleared = await redis_cache.clear_pattern("nasa:*") logger.info(f"โœ“ Redis cache cleared ({positions_cleared + nasa_cleared} keys)") total_cleared = positions_cleared + nasa_cleared return { "message": f"All caches cleared successfully ({total_cleared} Redis keys deleted)", "memory_cache": "cleared", "redis_cache": { "positions_keys": positions_cleared, "nasa_keys": nasa_cleared, "total": total_cleared } } @router.get("/cache/stats") async def get_cache_stats(): """Get cache statistics""" redis_stats = await redis_cache.get_stats() return { "redis": redis_stats, "memory": { "description": "In-memory cache (process-level)", "note": "Statistics not available for in-memory cache" } } @router.post("/settings/init-defaults") async def initialize_default_settings( db: AsyncSession = Depends(get_db) ): """Initialize default system settings (admin use)""" await system_settings_service.initialize_default_settings(db) await db.commit() return {"message": "Default settings initialized successfully"}