feat: add settings service and config API routes
Add a key-value settings table (Setting model) with DB > env > default resolution chain. Replace hardcoded Ollama constants in ollama_service with dynamic _get_config() lookups. Add /api/config/ endpoints for settings CRUD and tag management (create, rename). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ from .routes import (
|
||||
dashboard_router,
|
||||
revenus_router,
|
||||
ia_router,
|
||||
config_router,
|
||||
)
|
||||
|
||||
app = FastAPI(
|
||||
@@ -43,6 +44,7 @@ app.include_router(analytics_router)
|
||||
app.include_router(dashboard_router)
|
||||
app.include_router(revenus_router)
|
||||
app.include_router(ia_router)
|
||||
app.include_router(config_router)
|
||||
|
||||
|
||||
# Health check endpoints (keep in main app)
|
||||
|
||||
@@ -7,6 +7,7 @@ from .analytics import router as analytics_router
|
||||
from .dashboard import router as dashboard_router
|
||||
from .revenus import router as revenus_router
|
||||
from .ia import router as ia_router
|
||||
from .config import router as config_router
|
||||
|
||||
__all__ = [
|
||||
"extraction_router",
|
||||
@@ -16,4 +17,5 @@ __all__ = [
|
||||
"dashboard_router",
|
||||
"revenus_router",
|
||||
"ia_router",
|
||||
"config_router",
|
||||
]
|
||||
|
||||
134
src/plesna_gerance/api/routes/config.py
Normal file
134
src/plesna_gerance/api/routes/config.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""Routes de configuration — Settings LLM et gestion des tags."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ...database import get_session, DatabaseService
|
||||
from ...database.models import Tag
|
||||
from ...services.settings_service import (
|
||||
get_all_settings,
|
||||
set_setting,
|
||||
delete_setting,
|
||||
SETTINGS_REGISTRY,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/config", tags=["config"])
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Schemas
|
||||
# ============================================================
|
||||
|
||||
|
||||
class SettingUpdate(BaseModel):
|
||||
value: str
|
||||
|
||||
|
||||
class TagCreate(BaseModel):
|
||||
nom: str
|
||||
|
||||
|
||||
class TagUpdate(BaseModel):
|
||||
nom: str
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Settings endpoints
|
||||
# ============================================================
|
||||
|
||||
|
||||
@router.get("/settings")
|
||||
async def list_settings(
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""Liste tous les settings avec valeur et source."""
|
||||
return get_all_settings(session)
|
||||
|
||||
|
||||
@router.put("/settings/{key}")
|
||||
async def update_setting(
|
||||
key: str,
|
||||
body: SettingUpdate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""Met à jour un setting (persiste en DB)."""
|
||||
if key not in SETTINGS_REGISTRY:
|
||||
raise HTTPException(status_code=404, detail=f"Setting inconnu : {key}")
|
||||
set_setting(session, key, body.value)
|
||||
return {"key": key, "value": body.value, "source": "database"}
|
||||
|
||||
|
||||
@router.delete("/settings/{key}")
|
||||
async def reset_setting(
|
||||
key: str,
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""Reset un setting au défaut (supprime l'override DB)."""
|
||||
if key not in SETTINGS_REGISTRY:
|
||||
raise HTTPException(status_code=404, detail=f"Setting inconnu : {key}")
|
||||
deleted = delete_setting(session, key)
|
||||
# Return the resolved value after deletion
|
||||
all_settings = get_all_settings(session)
|
||||
return all_settings[key]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Tags endpoints
|
||||
# ============================================================
|
||||
|
||||
|
||||
@router.get("/tags")
|
||||
async def list_tags(
|
||||
session: Session = Depends(get_session),
|
||||
) -> list[dict]:
|
||||
"""Liste tous les tags."""
|
||||
db_service = DatabaseService(session)
|
||||
tags = db_service.list_tags()
|
||||
return [{"id": tag.id, "nom": tag.nom} for tag in tags]
|
||||
|
||||
|
||||
@router.post("/tags", status_code=201)
|
||||
async def create_tag(
|
||||
body: TagCreate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""Crée un nouveau tag (validation unicité)."""
|
||||
nom = body.nom.strip()
|
||||
if not nom:
|
||||
raise HTTPException(status_code=400, detail="Le nom du tag ne peut pas être vide.")
|
||||
|
||||
existing = session.query(Tag).filter(Tag.nom == nom).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=409, detail=f"Le tag '{nom}' existe déjà.")
|
||||
|
||||
tag = Tag(nom=nom)
|
||||
session.add(tag)
|
||||
session.commit()
|
||||
session.refresh(tag)
|
||||
return {"id": tag.id, "nom": tag.nom}
|
||||
|
||||
|
||||
@router.put("/tags/{tag_id}")
|
||||
async def rename_tag(
|
||||
tag_id: int,
|
||||
body: TagUpdate,
|
||||
session: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""Renomme un tag (validation unicité)."""
|
||||
nom = body.nom.strip()
|
||||
if not nom:
|
||||
raise HTTPException(status_code=400, detail="Le nom du tag ne peut pas être vide.")
|
||||
|
||||
tag = session.query(Tag).filter(Tag.id == tag_id).first()
|
||||
if not tag:
|
||||
raise HTTPException(status_code=404, detail="Tag introuvable.")
|
||||
|
||||
existing = session.query(Tag).filter(Tag.nom == nom, Tag.id != tag_id).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=409, detail=f"Le tag '{nom}' existe déjà.")
|
||||
|
||||
tag.nom = nom
|
||||
session.commit()
|
||||
session.refresh(tag)
|
||||
return {"id": tag.id, "nom": tag.nom}
|
||||
Reference in New Issue
Block a user