Files
roboco/roboco/api/routes/settings.py
T
Renn F f27a9f9447 feat(settings): panel-tunable feature flags
Add a Feature Flags card to the Settings page that toggles env-gated
subsystems (external/internal PR review, web research, strategy engine,
pitch provisioning, RAG auto-update, transcript pruning) directly from
the panel instead of hand-editing environment variables.

Flags persist in system_settings as 'true'/'false' and are overlaid onto
the live config singleton at startup; an unset flag keeps its
environment/config default. A toggle takes effect on the next backend
restart — no per-consumer re-routing.

Backend: FEATURE_FLAGS registry + bool validator + get_bool accessor on
SettingsService; feature_flag_effective_values and
apply_persisted_feature_flags; GET /settings/feature-flags; best-effort
startup overlay in the app lifespan.

Frontend: settingsApi.getFeatureFlags / setFeatureFlag and a
FeatureFlagsCard rendered full-width below the settings grid.
2026-06-17 16:50:11 +02:00

60 lines
2.0 KiB
Python

"""System settings API — read and update runtime-editable settings.
Backs the panel settings page. Values persist in the ``system_settings`` table
and are read by the backend (e.g. the transcript-retention prune sweep) with a
``roboco.config`` default as the fallback when a key is unset.
"""
from fastapi import APIRouter, HTTPException, status
from roboco.api.deps import DbSession
from roboco.api.schemas.settings import (
FeatureFlag,
FeatureFlagsResponse,
SettingsResponse,
SettingUpdate,
)
from roboco.services.settings import (
FEATURE_FLAGS,
SettingValidationError,
feature_flag_effective_values,
get_settings_service,
)
router = APIRouter()
@router.get("", response_model=SettingsResponse)
async def list_settings(db: DbSession) -> SettingsResponse:
"""Return every stored runtime-editable setting."""
return SettingsResponse(settings=await get_settings_service(db).all())
@router.get("/feature-flags", response_model=FeatureFlagsResponse)
async def get_feature_flags(db: DbSession) -> FeatureFlagsResponse:
"""Effective feature-flag values (stored override, else the env default)."""
effective = await feature_flag_effective_values(db)
return FeatureFlagsResponse(
flags=[
FeatureFlag(key=key, label=label, enabled=effective.get(key, False))
for key, label in FEATURE_FLAGS
]
)
@router.put("/{key}", response_model=SettingsResponse)
async def update_setting(
key: str, data: SettingUpdate, db: DbSession
) -> SettingsResponse:
"""Validate and persist a single setting, returning the full updated map."""
service = get_settings_service(db)
try:
await service.set(key, data.value)
except SettingValidationError as exc:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)
) from exc
# Write route commits explicitly (get_db auto-commit is unreliable).
await db.commit()
return SettingsResponse(settings=await service.all())