diff --git a/backend/main.py b/backend/main.py index 07c8f0a5..f1d8712c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,7 +7,7 @@ from slowapi.util import get_remote_address from . import models from .database import engine 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 .scheduler import scheduler, sync_scheduler_config @@ -91,7 +91,8 @@ app.include_router(auth.router) app.include_router(sync.router) app.include_router(categories.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") def startup_event(): diff --git a/backend/routers/admin/config.py b/backend/routers/admin/ai_config.py similarity index 74% rename from backend/routers/admin/config.py rename to backend/routers/admin/ai_config.py index 5cd2895d..a32242bc 100644 --- a/backend/routers/admin/config.py +++ b/backend/routers/admin/ai_config.py @@ -3,56 +3,16 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from ... import models, schemas, auth from ...database import get_db, BASE_DIR -from ...scheduler import sync_scheduler_config from ...config_manager import ConfigManager router = APIRouter( - prefix="/admin/db", + prefix="/admin/ai", tags=["Admin Configuration"] ) PROJECT_ROOT = os.path.dirname(BASE_DIR) 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") def get_ai_prompt( @@ -72,6 +32,7 @@ def get_ai_prompt( return {"value": "", "source": "none"} return {"value": setting.value, "source": "database"} + @router.post("/settings/prompt") def update_ai_prompt( payload: dict, @@ -82,7 +43,7 @@ def update_ai_prompt( value = payload.get("value") if value is None: raise HTTPException(status_code=400, detail="Value required") - + try: os.makedirs(os.path.dirname(PROMPT_FILE_PATH), exist_ok=True) with open(PROMPT_FILE_PATH, 'w', encoding='utf-8') as f: @@ -95,11 +56,12 @@ def update_ai_prompt( existing.value = value else: db.add(models.SystemSetting(key="ai_extraction_prompt", value=value)) - + db.commit() return {"status": "success", "file_updated": os.path.exists(PROMPT_FILE_PATH)} -@router.get("/settings/ai") + +@router.get("/settings") def get_ai_config( db: Session = Depends(get_db), current_admin: auth.TokenData = Depends(auth.get_current_admin) @@ -107,10 +69,10 @@ def get_ai_config( """Check AI provider status and active provider.""" gemini_key = os.environ.get("GEMINI_API_KEY") claude_key = os.environ.get("CLAUDE_API_KEY") - + provider_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first() active_provider = provider_setting.value if provider_setting else "gemini" - + return { "active_provider": active_provider, "providers": [ @@ -131,7 +93,8 @@ def get_ai_config( ] } -@router.post("/settings/ai-keys") + +@router.post("/settings/keys") def update_ai_keys( payload: dict, current_admin: auth.TokenData = Depends(auth.get_current_admin) @@ -139,23 +102,24 @@ def update_ai_keys( """Update AI API keys.""" gemini_key = payload.get("gemini_api_key") claude_key = payload.get("claude_api_key") - + updates = {} if gemini_key: updates["GEMINI_API_KEY"] = gemini_key if claude_key: updates["CLAUDE_API_KEY"] = claude_key - + if updates: ConfigManager.update_keys(updates) - + return { - "status": "success", + "status": "success", "gemini_configured": bool(os.environ.get("GEMINI_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( payload: dict, current_admin: auth.TokenData = Depends(auth.get_current_admin) @@ -163,13 +127,13 @@ def test_ai_key( """Test AI API key connectivity.""" provider = payload.get("provider") key = payload.get("key") - + if not provider or provider not in ["gemini", "claude"]: raise HTTPException(status_code=400, detail="Invalid provider") - + if not key or "****" in key: key = os.environ.get("GEMINI_API_KEY" if provider == "gemini" else "CLAUDE_API_KEY") - + if not key: raise HTTPException(status_code=400, detail="No API key provided or configured") @@ -187,7 +151,8 @@ def test_ai_key( except Exception as 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( payload: dict, db: Session = Depends(get_db), @@ -197,12 +162,12 @@ def update_ai_provider( provider = payload.get("provider") if provider not in ["gemini", "claude"]: raise HTTPException(status_code=400, detail="Invalid provider") - + existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first() if existing: existing.value = provider else: db.add(models.SystemSetting(key="ai_provider", value=provider)) - + db.commit() return {"status": "success", "active_provider": provider} diff --git a/backend/routers/admin/db_config.py b/backend/routers/admin/db_config.py new file mode 100644 index 00000000..400bf298 --- /dev/null +++ b/backend/routers/admin/db_config.py @@ -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 diff --git a/backend/tests/test_admin.py b/backend/tests/test_admin.py index 8207b25e..5cdcec58 100644 --- a/backend/tests/test_admin.py +++ b/backend/tests/test_admin.py @@ -38,7 +38,7 @@ def test_db_settings_workflow(test_client, admin_token): def test_ai_config(test_client, admin_token): response = test_client.get( - "/admin/db/settings/ai", + "/admin/ai/settings", headers={"Authorization": f"Bearer {admin_token}"} ) assert response.status_code == status.HTTP_200_OK