from fastapi import APIRouter, Depends, Query, status from sqlalchemy import select from sqlalchemy.orm import Session from app.api.deps import get_current_user, require_roles from app.api.deps import get_log_service from app.api.deps import get_service_control_service from app.api.deps import get_server_runtime_service from app.core.errors import raise_api_error from app.db.session import get_db from app.models.action import Action from app.models.managed_service import ManagedService from app.models.server import Server from app.models.user import User from app.schemas.action import ActionRead from app.schemas.logs import ServerLogRead, ServerLogSourcesRead from app.schemas.managed_service import ( ManagedServiceCreate, ManagedServiceRead, ManagedServiceUpdate, ) from app.schemas.server import ( ServerCreate, ServerHealthRead, ServerMetricsRead, ServerRead, ServerUpdate, ) from app.services.log_service import LogService from app.services.service_control import ServiceControlService from app.services.server_runtime import ServerRuntimeService router = APIRouter() @router.get("", response_model=list[ServerRead]) def list_servers( _: User = Depends(get_current_user), db: Session = Depends(get_db), ) -> list[Server]: return list(db.scalars(select(Server).order_by(Server.name))) @router.post("", response_model=ServerRead, status_code=status.HTTP_201_CREATED) def create_server( payload: ServerCreate, _: User = Depends(require_roles("admin")), db: Session = Depends(get_db), ) -> Server: existing_server = db.scalar(select(Server).where(Server.name == payload.name)) if existing_server is not None: raise_api_error( status_code=status.HTTP_409_CONFLICT, code="server_name_taken", message="A server with this name already exists.", ) server = Server(**payload.model_dump()) db.add(server) db.commit() db.refresh(server) return server @router.get("/{server_id}", response_model=ServerRead) def get_server( server_id: int, _: User = Depends(get_current_user), db: Session = Depends(get_db), ) -> Server: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) return server @router.patch("/{server_id}", response_model=ServerRead) def update_server( server_id: int, payload: ServerUpdate, _: User = Depends(require_roles("admin")), db: Session = Depends(get_db), ) -> Server: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) if payload.name is not None and payload.name != server.name: existing_server = db.scalar(select(Server).where(Server.name == payload.name)) if existing_server is not None: raise_api_error( status_code=status.HTTP_409_CONFLICT, code="server_name_taken", message="A server with this name already exists.", ) for field, value in payload.model_dump(exclude_unset=True).items(): setattr(server, field, value) db.add(server) db.commit() db.refresh(server) return server @router.delete("/{server_id}") def delete_server( server_id: int, _: User = Depends(require_roles("admin")), db: Session = Depends(get_db), ) -> dict[str, bool]: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) server.is_active = False db.add(server) db.commit() return {"success": True} @router.get("/{server_id}/health", response_model=ServerHealthRead) def get_server_health( server_id: int, _: User = Depends(get_current_user), runtime_service: ServerRuntimeService = Depends(get_server_runtime_service), db: Session = Depends(get_db), ) -> ServerHealthRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) return runtime_service.get_server_health(server) @router.get("/{server_id}/metrics", response_model=ServerMetricsRead) def get_server_metrics( server_id: int, _: User = Depends(get_current_user), runtime_service: ServerRuntimeService = Depends(get_server_runtime_service), db: Session = Depends(get_db), ) -> ServerMetricsRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) return runtime_service.get_server_metrics(server) @router.post("/{server_id}/services", response_model=ManagedServiceRead, status_code=status.HTTP_201_CREATED) def create_managed_service( server_id: int, payload: ManagedServiceCreate, _: User = Depends(require_roles("admin")), control_service: ServiceControlService = Depends(get_service_control_service), db: Session = Depends(get_db), ) -> ManagedServiceRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) managed_service = control_service.create_managed_service( db, server=server, name=payload.name, service_type=payload.service_type, control_enabled=payload.control_enabled, log_source=payload.log_source, ) return control_service.get_managed_service(server, managed_service) @router.get("/{server_id}/services", response_model=list[ManagedServiceRead]) def list_managed_services( server_id: int, _: User = Depends(get_current_user), control_service: ServiceControlService = Depends(get_service_control_service), db: Session = Depends(get_db), ) -> list[ManagedServiceRead]: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) return control_service.list_managed_services(db, server) @router.get("/{server_id}/services/{service_name}", response_model=ManagedServiceRead) def get_managed_service( server_id: int, service_name: str, _: User = Depends(get_current_user), control_service: ServiceControlService = Depends(get_service_control_service), db: Session = Depends(get_db), ) -> ManagedServiceRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) managed_service = db.scalar( select(ManagedService).where( ManagedService.server_id == server.id, ManagedService.name == service_name, ) ) if managed_service is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="service_not_found", message="Requested managed service was not found.", ) return control_service.get_managed_service(server, managed_service) @router.patch("/{server_id}/services/{service_name}", response_model=ManagedServiceRead) def update_managed_service( server_id: int, service_name: str, payload: ManagedServiceUpdate, _: User = Depends(require_roles("admin")), control_service: ServiceControlService = Depends(get_service_control_service), db: Session = Depends(get_db), ) -> ManagedServiceRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) managed_service = db.scalar( select(ManagedService).where( ManagedService.server_id == server.id, ManagedService.name == service_name, ) ) if managed_service is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="service_not_found", message="Requested managed service was not found.", ) updated_service = control_service.update_managed_service( db, managed_service, name=payload.name, service_type=payload.service_type, control_enabled=payload.control_enabled, log_source=payload.log_source, update_log_source="log_source" in payload.model_fields_set, ) return control_service.get_managed_service(server, updated_service) @router.post("/{server_id}/services/{service_name}/{action_name}", response_model=ActionRead) def run_service_action( server_id: int, service_name: str, action_name: str, current_user: User = Depends(require_roles("admin", "operator")), control_service: ServiceControlService = Depends(get_service_control_service), db: Session = Depends(get_db), ) -> Action: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) managed_service = db.scalar( select(ManagedService).where( ManagedService.server_id == server.id, ManagedService.name == service_name, ) ) if managed_service is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="service_not_found", message="Requested managed service was not found.", ) return control_service.run_service_action( db, server=server, managed_service=managed_service, action_name=action_name, actor=current_user, ) @router.get("/{server_id}/logs", response_model=ServerLogSourcesRead) def list_server_logs( server_id: int, _: User = Depends(get_current_user), log_service: LogService = Depends(get_log_service), db: Session = Depends(get_db), ) -> ServerLogSourcesRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) managed_services = list( db.scalars( select(ManagedService) .where(ManagedService.server_id == server.id) .order_by(ManagedService.name) ) ) return log_service.list_log_sources(server, managed_services) @router.get("/{server_id}/logs/{service_name}", response_model=ServerLogRead) def get_server_logs( server_id: int, service_name: str, _: User = Depends(get_current_user), log_service: LogService = Depends(get_log_service), db: Session = Depends(get_db), lines: int = Query(default=100, ge=1, le=500), ) -> ServerLogRead: server = db.get(Server, server_id) if server is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="server_not_found", message="Requested server was not found.", ) managed_service = db.scalar( select(ManagedService).where( ManagedService.server_id == server.id, ManagedService.name == service_name, ) ) if managed_service is None: raise_api_error( status_code=status.HTTP_404_NOT_FOUND, code="service_not_found", message="Requested managed service was not found.", ) return log_service.read_logs( server=server, managed_service=managed_service, lines=lines, )