refactor: split admin/config.py into ai_config and db_config

This commit is contained in:
2026-04-19 17:10:56 +03:00
parent 239368e595
commit 8fcd4150e5
4 changed files with 79 additions and 61 deletions

View File

@@ -7,7 +7,7 @@ from slowapi.util import get_remote_address
from . import models from . import models
from .database import engine from .database import engine
from .routers import items, operations, users, auth, sync, categories from .routers import items, operations, users, auth, sync, categories
from .routers.admin import backups, config from .routers.admin import backups, ai_config, db_config
from .logger import log from .logger import log
from .scheduler import scheduler, sync_scheduler_config from .scheduler import scheduler, sync_scheduler_config
@@ -91,7 +91,8 @@ app.include_router(auth.router)
app.include_router(sync.router) app.include_router(sync.router)
app.include_router(categories.router) app.include_router(categories.router)
app.include_router(backups.router) app.include_router(backups.router)
app.include_router(config.router) app.include_router(ai_config.router)
app.include_router(db_config.router)
@app.on_event("startup") @app.on_event("startup")
def startup_event(): def startup_event():

View File

@@ -3,56 +3,16 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from ... import models, schemas, auth from ... import models, schemas, auth
from ...database import get_db, BASE_DIR from ...database import get_db, BASE_DIR
from ...scheduler import sync_scheduler_config
from ...config_manager import ConfigManager from ...config_manager import ConfigManager
router = APIRouter( router = APIRouter(
prefix="/admin/db", prefix="/admin/ai",
tags=["Admin Configuration"] tags=["Admin Configuration"]
) )
PROJECT_ROOT = os.path.dirname(BASE_DIR) PROJECT_ROOT = os.path.dirname(BASE_DIR)
PROMPT_FILE_PATH = os.path.join(PROJECT_ROOT, "config", "ai_prompt.md") PROMPT_FILE_PATH = os.path.join(PROJECT_ROOT, "config", "ai_prompt.md")
@router.get("/settings", response_model=schemas.DbSettingsUpdate)
def get_db_settings(
db: Session = Depends(get_db),
current_admin: auth.TokenData = Depends(auth.get_current_admin)
):
"""Get database retention and scheduling settings."""
retention = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_retention_count").first()
hour = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_schedule_hour").first()
freq = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_schedule_freq_days").first()
return {
"retention_count": int(retention.value) if retention else 10,
"schedule_hour": int(hour.value) if hour else 3,
"schedule_freq_days": int(freq.value) if freq else 1
}
@router.patch("/settings", response_model=schemas.DbSettingsUpdate)
def update_db_settings(
settings: schemas.DbSettingsUpdate,
db: Session = Depends(get_db),
current_admin: auth.TokenData = Depends(auth.get_current_admin)
):
"""Update database settings and re-trigger scheduler sync."""
pairs = {
"backup_retention_count": str(settings.retention_count),
"backup_schedule_hour": str(settings.schedule_hour),
"backup_schedule_freq_days": str(settings.schedule_freq_days)
}
for key, val in pairs.items():
existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == key).first()
if existing:
existing.value = val
else:
db.add(models.SystemSetting(key=key, value=val))
db.commit()
sync_scheduler_config()
return settings
@router.get("/settings/prompt") @router.get("/settings/prompt")
def get_ai_prompt( def get_ai_prompt(
@@ -72,6 +32,7 @@ def get_ai_prompt(
return {"value": "", "source": "none"} return {"value": "", "source": "none"}
return {"value": setting.value, "source": "database"} return {"value": setting.value, "source": "database"}
@router.post("/settings/prompt") @router.post("/settings/prompt")
def update_ai_prompt( def update_ai_prompt(
payload: dict, payload: dict,
@@ -82,7 +43,7 @@ def update_ai_prompt(
value = payload.get("value") value = payload.get("value")
if value is None: if value is None:
raise HTTPException(status_code=400, detail="Value required") raise HTTPException(status_code=400, detail="Value required")
try: try:
os.makedirs(os.path.dirname(PROMPT_FILE_PATH), exist_ok=True) os.makedirs(os.path.dirname(PROMPT_FILE_PATH), exist_ok=True)
with open(PROMPT_FILE_PATH, 'w', encoding='utf-8') as f: with open(PROMPT_FILE_PATH, 'w', encoding='utf-8') as f:
@@ -95,11 +56,12 @@ def update_ai_prompt(
existing.value = value existing.value = value
else: else:
db.add(models.SystemSetting(key="ai_extraction_prompt", value=value)) db.add(models.SystemSetting(key="ai_extraction_prompt", value=value))
db.commit() db.commit()
return {"status": "success", "file_updated": os.path.exists(PROMPT_FILE_PATH)} return {"status": "success", "file_updated": os.path.exists(PROMPT_FILE_PATH)}
@router.get("/settings/ai")
@router.get("/settings")
def get_ai_config( def get_ai_config(
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_admin: auth.TokenData = Depends(auth.get_current_admin) current_admin: auth.TokenData = Depends(auth.get_current_admin)
@@ -107,10 +69,10 @@ def get_ai_config(
"""Check AI provider status and active provider.""" """Check AI provider status and active provider."""
gemini_key = os.environ.get("GEMINI_API_KEY") gemini_key = os.environ.get("GEMINI_API_KEY")
claude_key = os.environ.get("CLAUDE_API_KEY") claude_key = os.environ.get("CLAUDE_API_KEY")
provider_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first() provider_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first()
active_provider = provider_setting.value if provider_setting else "gemini" active_provider = provider_setting.value if provider_setting else "gemini"
return { return {
"active_provider": active_provider, "active_provider": active_provider,
"providers": [ "providers": [
@@ -131,7 +93,8 @@ def get_ai_config(
] ]
} }
@router.post("/settings/ai-keys")
@router.post("/settings/keys")
def update_ai_keys( def update_ai_keys(
payload: dict, payload: dict,
current_admin: auth.TokenData = Depends(auth.get_current_admin) current_admin: auth.TokenData = Depends(auth.get_current_admin)
@@ -139,23 +102,24 @@ def update_ai_keys(
"""Update AI API keys.""" """Update AI API keys."""
gemini_key = payload.get("gemini_api_key") gemini_key = payload.get("gemini_api_key")
claude_key = payload.get("claude_api_key") claude_key = payload.get("claude_api_key")
updates = {} updates = {}
if gemini_key: if gemini_key:
updates["GEMINI_API_KEY"] = gemini_key updates["GEMINI_API_KEY"] = gemini_key
if claude_key: if claude_key:
updates["CLAUDE_API_KEY"] = claude_key updates["CLAUDE_API_KEY"] = claude_key
if updates: if updates:
ConfigManager.update_keys(updates) ConfigManager.update_keys(updates)
return { return {
"status": "success", "status": "success",
"gemini_configured": bool(os.environ.get("GEMINI_API_KEY")), "gemini_configured": bool(os.environ.get("GEMINI_API_KEY")),
"claude_configured": bool(os.environ.get("CLAUDE_API_KEY")) "claude_configured": bool(os.environ.get("CLAUDE_API_KEY"))
} }
@router.post("/settings/test-ai-key")
@router.post("/settings/test-key")
def test_ai_key( def test_ai_key(
payload: dict, payload: dict,
current_admin: auth.TokenData = Depends(auth.get_current_admin) current_admin: auth.TokenData = Depends(auth.get_current_admin)
@@ -163,13 +127,13 @@ def test_ai_key(
"""Test AI API key connectivity.""" """Test AI API key connectivity."""
provider = payload.get("provider") provider = payload.get("provider")
key = payload.get("key") key = payload.get("key")
if not provider or provider not in ["gemini", "claude"]: if not provider or provider not in ["gemini", "claude"]:
raise HTTPException(status_code=400, detail="Invalid provider") raise HTTPException(status_code=400, detail="Invalid provider")
if not key or "****" in key: if not key or "****" in key:
key = os.environ.get("GEMINI_API_KEY" if provider == "gemini" else "CLAUDE_API_KEY") key = os.environ.get("GEMINI_API_KEY" if provider == "gemini" else "CLAUDE_API_KEY")
if not key: if not key:
raise HTTPException(status_code=400, detail="No API key provided or configured") raise HTTPException(status_code=400, detail="No API key provided or configured")
@@ -187,7 +151,8 @@ def test_ai_key(
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"{provider.capitalize()} Test Failed: {str(e)}") raise HTTPException(status_code=400, detail=f"{provider.capitalize()} Test Failed: {str(e)}")
@router.post("/settings/ai")
@router.post("/settings")
def update_ai_provider( def update_ai_provider(
payload: dict, payload: dict,
db: Session = Depends(get_db), db: Session = Depends(get_db),
@@ -197,12 +162,12 @@ def update_ai_provider(
provider = payload.get("provider") provider = payload.get("provider")
if provider not in ["gemini", "claude"]: if provider not in ["gemini", "claude"]:
raise HTTPException(status_code=400, detail="Invalid provider") raise HTTPException(status_code=400, detail="Invalid provider")
existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first() existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first()
if existing: if existing:
existing.value = provider existing.value = provider
else: else:
db.add(models.SystemSetting(key="ai_provider", value=provider)) db.add(models.SystemSetting(key="ai_provider", value=provider))
db.commit() db.commit()
return {"status": "success", "active_provider": provider} return {"status": "success", "active_provider": provider}

View File

@@ -0,0 +1,52 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from ... import models, schemas, auth
from ...database import get_db
from ...scheduler import sync_scheduler_config
router = APIRouter(
prefix="/admin/db",
tags=["Admin Configuration"]
)
@router.get("/settings", response_model=schemas.DbSettingsUpdate)
def get_db_settings(
db: Session = Depends(get_db),
current_admin: auth.TokenData = Depends(auth.get_current_admin)
):
"""Get database retention and scheduling settings."""
retention = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_retention_count").first()
hour = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_schedule_hour").first()
freq = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_schedule_freq_days").first()
return {
"retention_count": int(retention.value) if retention else 10,
"schedule_hour": int(hour.value) if hour else 3,
"schedule_freq_days": int(freq.value) if freq else 1
}
@router.patch("/settings", response_model=schemas.DbSettingsUpdate)
def update_db_settings(
settings: schemas.DbSettingsUpdate,
db: Session = Depends(get_db),
current_admin: auth.TokenData = Depends(auth.get_current_admin)
):
"""Update database settings and re-trigger scheduler sync."""
pairs = {
"backup_retention_count": str(settings.retention_count),
"backup_schedule_hour": str(settings.schedule_hour),
"backup_schedule_freq_days": str(settings.schedule_freq_days)
}
for key, val in pairs.items():
existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == key).first()
if existing:
existing.value = val
else:
db.add(models.SystemSetting(key=key, value=val))
db.commit()
sync_scheduler_config()
return settings

View File

@@ -38,7 +38,7 @@ def test_db_settings_workflow(test_client, admin_token):
def test_ai_config(test_client, admin_token): def test_ai_config(test_client, admin_token):
response = test_client.get( response = test_client.get(
"/admin/db/settings/ai", "/admin/ai/settings",
headers={"Authorization": f"Bearer {admin_token}"} headers={"Authorization": f"Bearer {admin_token}"}
) )
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK