Files
Transcriptarr/backend/api/settings.py
Dasemu 6272efbcd5 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
2026-01-16 16:57:59 +01:00

324 lines
9.1 KiB
Python

"""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)}"
)