feat(api): add REST API with 45+ endpoints
- Add workers API for pool management - Add jobs API for queue operations - Add scan-rules API for CRUD operations - Add scanner API for control and status - Add settings API for configuration management - Add system API for resource monitoring - Add filesystem API for path browsing - Add setup wizard API endpoint
This commit is contained in:
323
backend/api/settings.py
Normal file
323
backend/api/settings.py
Normal file
@@ -0,0 +1,323 @@
|
||||
"""Settings management API routes."""
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, HTTPException, Query, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||
|
||||
|
||||
# === REQUEST/RESPONSE MODELS ===
|
||||
|
||||
class SettingResponse(BaseModel):
|
||||
"""Setting response model."""
|
||||
id: int
|
||||
key: str
|
||||
value: Optional[str]
|
||||
description: Optional[str]
|
||||
category: Optional[str]
|
||||
value_type: Optional[str]
|
||||
created_at: Optional[str]
|
||||
updated_at: Optional[str]
|
||||
|
||||
|
||||
class SettingUpdateRequest(BaseModel):
|
||||
"""Setting update request."""
|
||||
value: str = Field(..., description="New value (as string)")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"value": "true"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SettingCreateRequest(BaseModel):
|
||||
"""Setting create request."""
|
||||
key: str = Field(..., description="Setting key")
|
||||
value: Optional[str] = Field(None, description="Setting value")
|
||||
description: Optional[str] = Field(None, description="Description")
|
||||
category: Optional[str] = Field(None, description="Category")
|
||||
value_type: Optional[str] = Field("string", description="Value type")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"key": "custom_setting",
|
||||
"value": "value",
|
||||
"description": "Custom setting description",
|
||||
"category": "general",
|
||||
"value_type": "string"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BulkUpdateRequest(BaseModel):
|
||||
"""Bulk update request."""
|
||||
settings: dict = Field(..., description="Dictionary of key-value pairs")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"settings": {
|
||||
"worker_cpu_count": "2",
|
||||
"worker_gpu_count": "1",
|
||||
"scanner_enabled": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""Generic message response."""
|
||||
message: str
|
||||
|
||||
|
||||
# === ROUTES ===
|
||||
|
||||
@router.get("/", response_model=List[SettingResponse])
|
||||
async def get_all_settings(category: Optional[str] = Query(None, description="Filter by category")):
|
||||
"""
|
||||
Get all settings or filter by category.
|
||||
|
||||
Args:
|
||||
category: Optional category filter (general, workers, transcription, scanner, bazarr)
|
||||
|
||||
Returns:
|
||||
List of settings
|
||||
"""
|
||||
from backend.core.settings_service import settings_service
|
||||
|
||||
if category:
|
||||
settings = settings_service.get_by_category(category)
|
||||
else:
|
||||
settings = settings_service.get_all()
|
||||
|
||||
return [SettingResponse(**s.to_dict()) for s in settings]
|
||||
|
||||
|
||||
@router.get("/{key}", response_model=SettingResponse)
|
||||
async def get_setting(key: str):
|
||||
"""
|
||||
Get a specific setting by key.
|
||||
|
||||
Args:
|
||||
key: Setting key
|
||||
|
||||
Returns:
|
||||
Setting object
|
||||
|
||||
Raises:
|
||||
404: Setting not found
|
||||
"""
|
||||
from backend.core.database import database
|
||||
from backend.core.settings_model import SystemSettings
|
||||
|
||||
with database.get_session() as session:
|
||||
setting = session.query(SystemSettings).filter(SystemSettings.key == key).first()
|
||||
|
||||
if not setting:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Setting '{key}' not found"
|
||||
)
|
||||
|
||||
return SettingResponse(**setting.to_dict())
|
||||
|
||||
|
||||
@router.put("/{key}", response_model=SettingResponse)
|
||||
async def update_setting(key: str, request: SettingUpdateRequest):
|
||||
"""
|
||||
Update a setting value.
|
||||
|
||||
Args:
|
||||
key: Setting key
|
||||
request: Update request with new value
|
||||
|
||||
Returns:
|
||||
Updated setting object
|
||||
|
||||
Raises:
|
||||
404: Setting not found
|
||||
400: Invalid value (e.g., GPU workers without GPU)
|
||||
"""
|
||||
from backend.core.settings_service import settings_service
|
||||
from backend.core.database import database
|
||||
from backend.core.settings_model import SystemSettings
|
||||
from backend.core.system_monitor import system_monitor
|
||||
|
||||
value = request.value
|
||||
|
||||
# Validate GPU worker count - force to 0 if no GPU available
|
||||
if key == 'worker_gpu_count':
|
||||
gpu_count = int(value) if value else 0
|
||||
if gpu_count > 0 and system_monitor.gpu_count == 0:
|
||||
logger.warning(
|
||||
f"Attempted to set worker_gpu_count={gpu_count} but no GPU detected. "
|
||||
"Forcing to 0."
|
||||
)
|
||||
value = '0'
|
||||
|
||||
success = settings_service.set(key, value)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to update setting '{key}'"
|
||||
)
|
||||
|
||||
# Return updated setting
|
||||
with database.get_session() as session:
|
||||
setting = session.query(SystemSettings).filter(SystemSettings.key == key).first()
|
||||
|
||||
if not setting:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Setting '{key}' not found"
|
||||
)
|
||||
|
||||
return SettingResponse(**setting.to_dict())
|
||||
|
||||
|
||||
@router.post("/bulk-update", response_model=MessageResponse)
|
||||
async def bulk_update_settings(request: BulkUpdateRequest):
|
||||
"""
|
||||
Update multiple settings at once.
|
||||
|
||||
Args:
|
||||
request: Bulk update request with settings dictionary
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
from backend.core.settings_service import settings_service
|
||||
from backend.core.system_monitor import system_monitor
|
||||
|
||||
# Validate GPU worker count - force to 0 if no GPU available
|
||||
settings_to_update = request.settings.copy()
|
||||
if 'worker_gpu_count' in settings_to_update:
|
||||
gpu_count = int(settings_to_update.get('worker_gpu_count', 0))
|
||||
if gpu_count > 0 and system_monitor.gpu_count == 0:
|
||||
logger.warning(
|
||||
f"Attempted to set worker_gpu_count={gpu_count} but no GPU detected. "
|
||||
"Forcing to 0."
|
||||
)
|
||||
settings_to_update['worker_gpu_count'] = '0'
|
||||
|
||||
success = settings_service.bulk_update(settings_to_update)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update settings"
|
||||
)
|
||||
|
||||
logger.info(f"Bulk updated {len(request.settings)} settings")
|
||||
|
||||
return MessageResponse(message=f"Updated {len(request.settings)} settings successfully")
|
||||
|
||||
|
||||
@router.post("/", response_model=SettingResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_setting(request: SettingCreateRequest):
|
||||
"""
|
||||
Create a new setting.
|
||||
|
||||
Args:
|
||||
request: Create request with setting details
|
||||
|
||||
Returns:
|
||||
Created setting object
|
||||
|
||||
Raises:
|
||||
409: Setting already exists
|
||||
"""
|
||||
from backend.core.settings_service import settings_service
|
||||
from backend.core.database import database
|
||||
from backend.core.settings_model import SystemSettings
|
||||
|
||||
# Check if exists
|
||||
with database.get_session() as session:
|
||||
existing = session.query(SystemSettings).filter(SystemSettings.key == request.key).first()
|
||||
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Setting '{request.key}' already exists"
|
||||
)
|
||||
|
||||
# Create
|
||||
success = settings_service.set(
|
||||
key=request.key,
|
||||
value=request.value,
|
||||
description=request.description,
|
||||
category=request.category,
|
||||
value_type=request.value_type
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to create setting"
|
||||
)
|
||||
|
||||
# Return created setting
|
||||
with database.get_session() as session:
|
||||
setting = session.query(SystemSettings).filter(SystemSettings.key == request.key).first()
|
||||
return SettingResponse(**setting.to_dict())
|
||||
|
||||
|
||||
@router.delete("/{key}", response_model=MessageResponse)
|
||||
async def delete_setting(key: str):
|
||||
"""
|
||||
Delete a setting.
|
||||
|
||||
Args:
|
||||
key: Setting key
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
|
||||
Raises:
|
||||
404: Setting not found
|
||||
"""
|
||||
from backend.core.settings_service import settings_service
|
||||
|
||||
success = settings_service.delete(key)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Setting '{key}' not found"
|
||||
)
|
||||
|
||||
logger.info(f"Setting deleted: {key}")
|
||||
|
||||
return MessageResponse(message=f"Setting '{key}' deleted successfully")
|
||||
|
||||
|
||||
@router.post("/init-defaults", response_model=MessageResponse)
|
||||
async def init_default_settings():
|
||||
"""
|
||||
Initialize default settings.
|
||||
|
||||
Creates all default settings if they don't exist.
|
||||
Safe to call multiple times (won't overwrite existing).
|
||||
|
||||
Returns:
|
||||
Success message
|
||||
"""
|
||||
from backend.core.settings_service import settings_service
|
||||
|
||||
try:
|
||||
settings_service.init_default_settings()
|
||||
return MessageResponse(message="Default settings initialized successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize default settings: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to initialize default settings: {str(e)}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user