mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-03 18:50:24 +00:00
186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.services.system_settings_service import (
|
|
ReadOnlySettingError,
|
|
bot_configuration_service,
|
|
)
|
|
|
|
from ..dependencies import get_db_session, require_api_token
|
|
from ..schemas.config import (
|
|
SettingCategoryRef,
|
|
SettingCategorySummary,
|
|
SettingChoice,
|
|
SettingDefinition,
|
|
SettingUpdateRequest,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _coerce_value(key: str, value: Any) -> Any:
|
|
definition = bot_configuration_service.get_definition(key)
|
|
|
|
if value is None:
|
|
if definition.is_optional:
|
|
return None
|
|
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Value is required")
|
|
|
|
python_type = definition.python_type
|
|
|
|
try:
|
|
if python_type is bool:
|
|
if isinstance(value, bool):
|
|
normalized = value
|
|
elif isinstance(value, str):
|
|
lowered = value.strip().lower()
|
|
if lowered in {"true", "1", "yes", "on", "да"}:
|
|
normalized = True
|
|
elif lowered in {"false", "0", "no", "off", "нет"}:
|
|
normalized = False
|
|
else:
|
|
raise ValueError("invalid bool")
|
|
else:
|
|
raise ValueError("invalid bool")
|
|
|
|
elif python_type is int:
|
|
normalized = int(value)
|
|
elif python_type is float:
|
|
normalized = float(value)
|
|
else:
|
|
normalized = str(value)
|
|
except ValueError:
|
|
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Invalid value type") from None
|
|
|
|
choices = bot_configuration_service.get_choice_options(key)
|
|
if choices:
|
|
allowed_values = {option.value for option in choices}
|
|
if normalized not in allowed_values:
|
|
readable = ", ".join(bot_configuration_service.format_value(opt.value) for opt in choices)
|
|
raise HTTPException(
|
|
status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Value must be one of: {readable}",
|
|
)
|
|
|
|
return normalized
|
|
|
|
|
|
def _serialize_definition(definition, include_choices: bool = True) -> SettingDefinition:
|
|
current = bot_configuration_service.get_current_value(definition.key)
|
|
original = bot_configuration_service.get_original_value(definition.key)
|
|
has_override = bot_configuration_service.has_override(definition.key)
|
|
|
|
choices: list[SettingChoice] = []
|
|
if include_choices:
|
|
choices = [
|
|
SettingChoice(
|
|
value=option.value,
|
|
label=option.label,
|
|
description=option.description,
|
|
)
|
|
for option in bot_configuration_service.get_choice_options(definition.key)
|
|
]
|
|
|
|
return SettingDefinition(
|
|
key=definition.key,
|
|
name=definition.display_name,
|
|
category=SettingCategoryRef(
|
|
key=definition.category_key,
|
|
label=definition.category_label,
|
|
),
|
|
type=definition.type_label,
|
|
is_optional=definition.is_optional,
|
|
current=current,
|
|
original=original,
|
|
has_override=has_override,
|
|
read_only=bot_configuration_service.is_read_only(definition.key),
|
|
choices=choices,
|
|
)
|
|
|
|
|
|
@router.get("/categories", response_model=list[SettingCategorySummary])
|
|
async def list_categories(
|
|
_: object = Security(require_api_token),
|
|
) -> list[SettingCategorySummary]:
|
|
categories = bot_configuration_service.get_categories()
|
|
return [
|
|
SettingCategorySummary(key=key, label=label, items=count)
|
|
for key, label, count in categories
|
|
]
|
|
|
|
|
|
@router.get("", response_model=list[SettingDefinition])
|
|
async def list_settings(
|
|
_: object = Security(require_api_token),
|
|
category: Optional[str] = Query(default=None, alias="category_key"),
|
|
) -> list[SettingDefinition]:
|
|
items: list[SettingDefinition] = []
|
|
if category:
|
|
definitions = bot_configuration_service.get_settings_for_category(category)
|
|
items.extend(_serialize_definition(defn) for defn in definitions)
|
|
return items
|
|
|
|
for category_key, _, _ in bot_configuration_service.get_categories():
|
|
definitions = bot_configuration_service.get_settings_for_category(category_key)
|
|
items.extend(_serialize_definition(defn) for defn in definitions)
|
|
|
|
return items
|
|
|
|
|
|
@router.get("/{key}", response_model=SettingDefinition)
|
|
async def get_setting(
|
|
key: str,
|
|
_: object = Security(require_api_token),
|
|
) -> SettingDefinition:
|
|
try:
|
|
definition = bot_configuration_service.get_definition(key)
|
|
except KeyError as error: # pragma: no cover - защита от некорректного ключа
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Setting not found") from error
|
|
|
|
return _serialize_definition(definition)
|
|
|
|
|
|
@router.put("/{key}", response_model=SettingDefinition)
|
|
async def update_setting(
|
|
key: str,
|
|
payload: SettingUpdateRequest,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> SettingDefinition:
|
|
try:
|
|
definition = bot_configuration_service.get_definition(key)
|
|
except KeyError as error:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Setting not found") from error
|
|
|
|
value = _coerce_value(key, payload.value)
|
|
try:
|
|
await bot_configuration_service.set_value(db, key, value)
|
|
except ReadOnlySettingError as error:
|
|
raise HTTPException(status.HTTP_403_FORBIDDEN, str(error)) from error
|
|
await db.commit()
|
|
|
|
return _serialize_definition(definition)
|
|
|
|
|
|
@router.delete("/{key}", response_model=SettingDefinition)
|
|
async def reset_setting(
|
|
key: str,
|
|
_: object = Security(require_api_token),
|
|
db: AsyncSession = Depends(get_db_session),
|
|
) -> SettingDefinition:
|
|
try:
|
|
definition = bot_configuration_service.get_definition(key)
|
|
except KeyError as error:
|
|
raise HTTPException(status.HTTP_404_NOT_FOUND, "Setting not found") from error
|
|
|
|
try:
|
|
await bot_configuration_service.reset_value(db, key)
|
|
except ReadOnlySettingError as error:
|
|
raise HTTPException(status.HTTP_403_FORBIDDEN, str(error)) from error
|
|
await db.commit()
|
|
return _serialize_definition(definition)
|