cosmo_backend/app/api/system.py

254 lines
7.5 KiB
Python

"""
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"}