""" Configuration management and server CRUD API endpoints. """ import logging from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database import get_db from app.models import DeployServer, GitServer from app.schemas import ( ConfigResponse, ConfigUpdate, DeployServerCreate, DeployServerResponse, GitServerCreate, GitServerResponse, ) log = logging.getLogger("foxy.api.config") router = APIRouter(prefix="/api/config", tags=["config"]) # ─── App Configuration ──────────────────────────────────────────────────────── @router.get("", response_model=ConfigResponse) async def get_config(): """Get current configuration (secrets masked).""" return ConfigResponse( FOXY_HOME=settings.FOXY_HOME, FOXY_WORKSPACE=settings.FOXY_WORKSPACE, GITEA_SERVER=settings.GITEA_SERVER, DEPLOYMENT_SERVER=settings.DEPLOYMENT_SERVER, DEPLOYMENT_USER=settings.DEPLOYMENT_USER, TELEGRAM_CHAT_ID=settings.TELEGRAM_CHAT_ID, LOG_LEVEL=settings.LOG_LEVEL, GITEA_OPENCLAW_TOKEN="***" if settings.GITEA_OPENCLAW_TOKEN else "", DEPLOYMENT_PWD="***" if settings.DEPLOYMENT_PWD else "", TELEGRAM_BOT_TOKEN="***" if settings.TELEGRAM_BOT_TOKEN else "", ) @router.put("") async def update_config(body: ConfigUpdate): """ Update non-sensitive configuration values. Note: In production, this would persist to .env file or a config store. For now, it updates the in-memory settings. """ updated = {} for field, value in body.model_dump(exclude_unset=True).items(): if value is not None: setattr(settings, field, value) updated[field] = value log.info(f"Config updated: {list(updated.keys())}") return {"message": "Configuration updated", "updated_fields": list(updated.keys())} # ─── Deploy Servers ──────────────────────────────────────────────────────────── @router.get("/deploy-servers", response_model=list[DeployServerResponse]) async def list_deploy_servers(db: AsyncSession = Depends(get_db)): """List all deployment servers.""" result = await db.execute(select(DeployServer).order_by(DeployServer.name)) return result.scalars().all() @router.post("/deploy-servers", response_model=DeployServerResponse, status_code=201) async def create_deploy_server(body: DeployServerCreate, db: AsyncSession = Depends(get_db)): """Add a new deployment server.""" server = DeployServer( name=body.name, host=body.host, user=body.user, password=body.password, ssh_port=body.ssh_port, description=body.description, ) db.add(server) await db.flush() await db.refresh(server) log.info(f"Deploy server created: {body.name} ({body.host})") return server @router.delete("/deploy-servers/{server_id}") async def delete_deploy_server(server_id: int, db: AsyncSession = Depends(get_db)): """Remove a deployment server.""" result = await db.execute(select(DeployServer).where(DeployServer.id == server_id)) server = result.scalar_one_or_none() if not server: raise HTTPException(status_code=404, detail="Deploy server not found") name = server.name await db.delete(server) await db.flush() log.info(f"Deploy server deleted: {name}") return {"message": f"Deploy server '{name}' deleted"} # ─── Git Servers ─────────────────────────────────────────────────────────────── @router.get("/git-servers", response_model=list[GitServerResponse]) async def list_git_servers(db: AsyncSession = Depends(get_db)): """List all Git servers.""" result = await db.execute(select(GitServer).order_by(GitServer.name)) return result.scalars().all() @router.post("/git-servers", response_model=GitServerResponse, status_code=201) async def create_git_server(body: GitServerCreate, db: AsyncSession = Depends(get_db)): """Add a new Git server.""" server = GitServer( name=body.name, url=body.url, token=body.token, org=body.org, description=body.description, ) db.add(server) await db.flush() await db.refresh(server) log.info(f"Git server created: {body.name} ({body.url})") return server @router.delete("/git-servers/{server_id}") async def delete_git_server(server_id: int, db: AsyncSession = Depends(get_db)): """Remove a Git server.""" result = await db.execute(select(GitServer).where(GitServer.id == server_id)) server = result.scalar_one_or_none() if not server: raise HTTPException(status_code=404, detail="Git server not found") name = server.name await db.delete(server) await db.flush() log.info(f"Git server deleted: {name}") return {"message": f"Git server '{name}' deleted"}