# [DEF:backend.src.api.routes.environments:Module] # # @TIER: STANDARD # @SEMANTICS: api, environments, superset, databases # @PURPOSE: API endpoints for listing environments and their databases. # @LAYER: API # @RELATION: DEPENDS_ON -> backend.src.dependencies # @RELATION: DEPENDS_ON -> backend.src.core.superset_client # # @INVARIANT: Environment IDs must exist in the configuration. # [SECTION: IMPORTS] from fastapi import APIRouter, Depends, HTTPException from typing import List, Optional from ...dependencies import get_config_manager, get_scheduler_service, has_permission from ...core.superset_client import SupersetClient from pydantic import BaseModel, Field from ...core.logger import belief_scope # [/SECTION] router = APIRouter(prefix="/api/environments", tags=["Environments"]) # [DEF:ScheduleSchema:DataClass] class ScheduleSchema(BaseModel): enabled: bool = False cron_expression: str = Field(..., pattern=r'^(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|((((\d+,)*\d+|(\d+(\/|-)\d+)|\d+|\*) ?){4,6})$') # [/DEF:ScheduleSchema:DataClass] # [DEF:EnvironmentResponse:DataClass] class EnvironmentResponse(BaseModel): id: str name: str url: str backup_schedule: Optional[ScheduleSchema] = None # [/DEF:EnvironmentResponse:DataClass] # [DEF:DatabaseResponse:DataClass] class DatabaseResponse(BaseModel): uuid: str database_name: str engine: Optional[str] # [/DEF:DatabaseResponse:DataClass] # [DEF:get_environments:Function] # @PURPOSE: List all configured environments. # @LAYER: API # @SEMANTICS: list, environments, config # @PRE: config_manager is injected via Depends. # @POST: Returns a list of EnvironmentResponse objects. # @RETURN: List[EnvironmentResponse] @router.get("", response_model=List[EnvironmentResponse]) async def get_environments( config_manager=Depends(get_config_manager), _ = Depends(has_permission("environments", "READ")) ): with belief_scope("get_environments"): envs = config_manager.get_environments() # Ensure envs is a list if not isinstance(envs, list): envs = [] return [ EnvironmentResponse( id=e.id, name=e.name, url=e.url, backup_schedule=ScheduleSchema( enabled=e.backup_schedule.enabled, cron_expression=e.backup_schedule.cron_expression ) if getattr(e, 'backup_schedule', None) else None ) for e in envs ] # [/DEF:get_environments:Function] # [DEF:update_environment_schedule:Function] # @PURPOSE: Update backup schedule for an environment. # @LAYER: API # @SEMANTICS: update, schedule, backup, environment # @PRE: Environment id exists, schedule is valid ScheduleSchema. # @POST: Backup schedule updated and scheduler reloaded. # @PARAM: id (str) - The environment ID. # @PARAM: schedule (ScheduleSchema) - The new schedule. @router.put("/{id}/schedule") async def update_environment_schedule( id: str, schedule: ScheduleSchema, config_manager=Depends(get_config_manager), scheduler_service=Depends(get_scheduler_service), _ = Depends(has_permission("admin:settings", "WRITE")) ): with belief_scope("update_environment_schedule", f"id={id}"): envs = config_manager.get_environments() env = next((e for e in envs if e.id == id), None) if not env: raise HTTPException(status_code=404, detail="Environment not found") # Update environment config env.backup_schedule.enabled = schedule.enabled env.backup_schedule.cron_expression = schedule.cron_expression config_manager.update_environment(id, env) # Refresh scheduler scheduler_service.load_schedules() return {"message": "Schedule updated successfully"} # [/DEF:update_environment_schedule:Function] # [DEF:get_environment_databases:Function] # @PURPOSE: Fetch the list of databases from a specific environment. # @LAYER: API # @SEMANTICS: fetch, databases, superset, environment # @PRE: Environment id exists. # @POST: Returns a list of database summaries from the environment. # @PARAM: id (str) - The environment ID. # @RETURN: List[Dict] - List of databases. @router.get("/{id}/databases") async def get_environment_databases( id: str, config_manager=Depends(get_config_manager), _ = Depends(has_permission("admin:settings", "READ")) ): with belief_scope("get_environment_databases", f"id={id}"): envs = config_manager.get_environments() env = next((e for e in envs if e.id == id), None) if not env: raise HTTPException(status_code=404, detail="Environment not found") try: # Initialize SupersetClient from environment config client = SupersetClient(env) return client.get_databases_summary() except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to fetch databases: {str(e)}") # [/DEF:get_environment_databases:Function] # [/DEF:backend.src.api.routes.environments:Module]