You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
6.0 KiB
168 lines
6.0 KiB
# -*- coding: utf-8 -*-
|
|
"""System configuration endpoints."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
|
|
from api.deps import get_system_config_service
|
|
from api.v1.schemas.common import ErrorResponse
|
|
from api.v1.schemas.system_config import (
|
|
SystemConfigConflictResponse,
|
|
SystemConfigResponse,
|
|
SystemConfigSchemaResponse,
|
|
SystemConfigValidationErrorResponse,
|
|
UpdateSystemConfigRequest,
|
|
UpdateSystemConfigResponse,
|
|
ValidateSystemConfigRequest,
|
|
ValidateSystemConfigResponse,
|
|
)
|
|
from src.services.system_config_service import ConfigConflictError, ConfigValidationError, SystemConfigService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get(
|
|
"/config",
|
|
response_model=SystemConfigResponse,
|
|
responses={
|
|
200: {"description": "Configuration loaded"},
|
|
401: {"description": "Unauthorized", "model": ErrorResponse},
|
|
500: {"description": "Internal server error", "model": ErrorResponse},
|
|
},
|
|
summary="Get system configuration",
|
|
description="Read current configuration from .env and return raw values.",
|
|
)
|
|
def get_system_config(
|
|
include_schema: bool = Query(True, description="Whether to include schema metadata"),
|
|
service: SystemConfigService = Depends(get_system_config_service),
|
|
) -> SystemConfigResponse:
|
|
"""Load and return current system configuration."""
|
|
try:
|
|
payload = service.get_config(include_schema=include_schema)
|
|
return SystemConfigResponse.model_validate(payload)
|
|
except Exception as exc:
|
|
logger.error("Failed to load system configuration: %s", exc, exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={
|
|
"error": "internal_error",
|
|
"message": "Failed to load system configuration",
|
|
},
|
|
)
|
|
|
|
|
|
@router.put(
|
|
"/config",
|
|
response_model=UpdateSystemConfigResponse,
|
|
responses={
|
|
200: {"description": "Configuration updated"},
|
|
400: {"description": "Validation failed", "model": SystemConfigValidationErrorResponse},
|
|
409: {"description": "Version conflict", "model": SystemConfigConflictResponse},
|
|
500: {"description": "Internal server error", "model": ErrorResponse},
|
|
},
|
|
summary="Update system configuration",
|
|
description="Update key-value pairs in .env. Mask token preserves existing secret values.",
|
|
)
|
|
def update_system_config(
|
|
request: UpdateSystemConfigRequest,
|
|
service: SystemConfigService = Depends(get_system_config_service),
|
|
) -> UpdateSystemConfigResponse:
|
|
"""Validate and persist system configuration updates."""
|
|
try:
|
|
payload = service.update(
|
|
config_version=request.config_version,
|
|
items=[item.model_dump() for item in request.items],
|
|
mask_token=request.mask_token,
|
|
reload_now=request.reload_now,
|
|
)
|
|
return UpdateSystemConfigResponse.model_validate(payload)
|
|
except ConfigValidationError as exc:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail={
|
|
"error": "validation_failed",
|
|
"message": "System configuration validation failed",
|
|
"issues": exc.issues,
|
|
},
|
|
)
|
|
except ConfigConflictError as exc:
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail={
|
|
"error": "config_version_conflict",
|
|
"message": "Configuration has changed, please reload and retry",
|
|
"current_config_version": exc.current_version,
|
|
},
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Failed to update system configuration: %s", exc, exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={
|
|
"error": "internal_error",
|
|
"message": "Failed to update system configuration",
|
|
},
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/config/validate",
|
|
response_model=ValidateSystemConfigResponse,
|
|
responses={
|
|
200: {"description": "Validation completed"},
|
|
500: {"description": "Internal server error", "model": ErrorResponse},
|
|
},
|
|
summary="Validate system configuration",
|
|
description="Validate submitted configuration values without writing to .env.",
|
|
)
|
|
def validate_system_config(
|
|
request: ValidateSystemConfigRequest,
|
|
service: SystemConfigService = Depends(get_system_config_service),
|
|
) -> ValidateSystemConfigResponse:
|
|
"""Run pre-save validation only."""
|
|
try:
|
|
payload = service.validate(items=[item.model_dump() for item in request.items])
|
|
return ValidateSystemConfigResponse.model_validate(payload)
|
|
except Exception as exc:
|
|
logger.error("Failed to validate system configuration: %s", exc, exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={
|
|
"error": "internal_error",
|
|
"message": "Failed to validate system configuration",
|
|
},
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/config/schema",
|
|
response_model=SystemConfigSchemaResponse,
|
|
responses={
|
|
200: {"description": "Schema loaded"},
|
|
500: {"description": "Internal server error", "model": ErrorResponse},
|
|
},
|
|
summary="Get system configuration schema",
|
|
description="Return categorized field metadata used for dynamic settings form rendering.",
|
|
)
|
|
def get_system_config_schema(
|
|
service: SystemConfigService = Depends(get_system_config_service),
|
|
) -> SystemConfigSchemaResponse:
|
|
"""Return schema metadata for system configuration fields."""
|
|
try:
|
|
payload = service.get_schema()
|
|
return SystemConfigSchemaResponse.model_validate(payload)
|
|
except Exception as exc:
|
|
logger.error("Failed to load system configuration schema: %s", exc, exc_info=True)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={
|
|
"error": "internal_error",
|
|
"message": "Failed to load system configuration schema",
|
|
},
|
|
)
|