mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-29 19:37:54 +08:00
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。 按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩, CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。 修复空值、异步和供应方类型边界,保持插件与 API 兼容合同, 并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
225 lines
7.7 KiB
Python
225 lines
7.7 KiB
Python
"""
|
||
系统设置查询、合同发现和受控更新接口。
|
||
"""
|
||
|
||
from typing import Annotated, Any, Optional, Union
|
||
|
||
from fastapi import Body, Depends, HTTPException, Query
|
||
|
||
from app.api.context import get_host_runtime
|
||
from app.api.dependencies.auth import get_current_active_superuser_async
|
||
from app.api.principal import ApiPrincipal
|
||
from app.api.response import ResponseAPIRouter
|
||
from app.application.configuration import (
|
||
get_configured_system_config,
|
||
get_runtime_settings,
|
||
)
|
||
from app.application.settings.service import (
|
||
SystemSettingConflictError,
|
||
SystemSettingsService,
|
||
)
|
||
from app.schemas.common import JsonObject as _SchemaJsonObject
|
||
from app.schemas.common import ValueData as _SchemaValueData
|
||
from app.schemas.response import Response as _SchemaResponse
|
||
from app.schemas.system import SystemSettingsUpdateRequest as _SchemaSystemSettingsUpdateRequest
|
||
from app.startup.composition.context import HostRuntime
|
||
|
||
router = ResponseAPIRouter()
|
||
|
||
|
||
def _settings_service(runtime: HostRuntime) -> SystemSettingsService:
|
||
"""
|
||
基于当前宿主运行时构造系统设置应用服务。
|
||
"""
|
||
return SystemSettingsService(
|
||
get_runtime_settings(),
|
||
get_configured_system_config(),
|
||
runtime.system.publish_config_changed,
|
||
)
|
||
|
||
|
||
@router.get(
|
||
"/setting/{key}",
|
||
summary="查询系统设置",
|
||
response_model=_SchemaResponse[_SchemaValueData],
|
||
)
|
||
async def get_setting(
|
||
key: str,
|
||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||
) -> _SchemaResponse[Any]:
|
||
"""
|
||
查询系统设置(仅管理员)。
|
||
"""
|
||
runtime_settings = get_runtime_settings()
|
||
if runtime_settings.contains(key):
|
||
value = runtime_settings.get(key)
|
||
else:
|
||
value = get_configured_system_config().get(key)
|
||
return _SchemaResponse(success=True, data={"value": value})
|
||
|
||
|
||
@router.post(
|
||
"/setting/{key}",
|
||
summary="更新系统设置",
|
||
response_model=_SchemaResponse[None],
|
||
)
|
||
async def set_setting(
|
||
key: str,
|
||
value: Annotated[Union[list[Any], dict[str, Any], bool, int, str] | None, Body()] = None,
|
||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||
runtime: HostRuntime = Depends(get_host_runtime),
|
||
) -> _SchemaResponse[Any]:
|
||
"""
|
||
更新系统设置(仅管理员)。
|
||
"""
|
||
result = await runtime.system.update_setting(key, value)
|
||
return _SchemaResponse(success=result.success, message=result.message or "")
|
||
|
||
|
||
@router.get(
|
||
"/settings/catalog",
|
||
summary='列出系统设置契约',
|
||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||
)
|
||
async def catalog_settings(
|
||
group: Annotated[
|
||
Optional[str],
|
||
Query(description='设置契约分组;使用 all 可列出全部已登记设置。'),
|
||
] = "all",
|
||
keyword: Annotated[
|
||
Optional[str],
|
||
Query(description='匹配设置键、分组、标签或说明的子串,不区分大小写。'),
|
||
] = None,
|
||
source: Annotated[
|
||
Optional[str],
|
||
Query(description='可选的来源筛选:settings 或 systemconfig。'),
|
||
] = None,
|
||
offset: Annotated[int, Query(ge=0, description='结果偏移量,从 0 开始。')] = 0,
|
||
limit: Annotated[int, Query(ge=1, le=500, description='最多返回的设置契约条数。')] = 100,
|
||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||
runtime: HostRuntime = Depends(get_host_runtime),
|
||
) -> _SchemaResponse[Any]:
|
||
"""
|
||
分页返回轻量设置合同,供 Agent 建立可用设置索引。
|
||
"""
|
||
try:
|
||
data = _settings_service(runtime).catalog(
|
||
group=group,
|
||
keyword=keyword,
|
||
source=source,
|
||
offset=offset,
|
||
limit=limit,
|
||
)
|
||
except ValueError as error:
|
||
return _SchemaResponse(success=False, message=str(error))
|
||
return _SchemaResponse(success=True, data=data)
|
||
|
||
|
||
@router.get(
|
||
"/settings/describe/{setting_key}",
|
||
summary='查询单项系统设置契约',
|
||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||
)
|
||
async def describe_setting(
|
||
setting_key: str,
|
||
include_value: Annotated[
|
||
bool,
|
||
Query(description='是否在完整契约之外返回当前值。'),
|
||
] = True,
|
||
show_secrets: Annotated[
|
||
bool,
|
||
Query(description='是否返回未脱敏的秘密值,仅可在明确授权后使用。'),
|
||
] = False,
|
||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||
runtime: HostRuntime = Depends(get_host_runtime),
|
||
) -> _SchemaResponse[Any]:
|
||
"""
|
||
返回单项完整设置合同、当前值和 revision。
|
||
"""
|
||
try:
|
||
data = _settings_service(runtime).describe(
|
||
setting_key=setting_key,
|
||
include_value=include_value,
|
||
show_secrets=show_secrets,
|
||
)
|
||
except ValueError as error:
|
||
return _SchemaResponse(success=False, message=str(error))
|
||
return _SchemaResponse(success=True, data=data)
|
||
|
||
|
||
@router.get(
|
||
"/settings",
|
||
summary='发现或读取已登记的系统设置',
|
||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||
)
|
||
async def query_settings(
|
||
setting_key: Annotated[
|
||
Optional[str],
|
||
Query(
|
||
description=(
|
||
'精确设置键,接受 APP_DOMAIN 或 LLM_MODEL 等 Settings 字段名、Downloaders 或 MediaServers 等 Syst'
|
||
'emConfigKey 值或枚举名,以及能唯一解析的别名;省略时按列表发现设置。'
|
||
)
|
||
),
|
||
] = None,
|
||
group: Annotated[
|
||
Optional[str],
|
||
Query(
|
||
description=(
|
||
'省略 setting_key 时使用的发现分组。调用 config.system.list 可获取完整、机器可读的分组索引;此兼容端点接受全部已登记分组,包括 '
|
||
'settings 和 systemconfig。'
|
||
)
|
||
),
|
||
] = "all",
|
||
keyword: Annotated[
|
||
Optional[str],
|
||
Query(description='用于发现匹配设置键、分组或标签的子串,不区分大小写。'),
|
||
] = None,
|
||
include_values: Annotated[
|
||
Optional[bool],
|
||
Query(description='是否返回完整值;查询单个精确设置键时默认为 True,发现设置列表时默认为 False。'),
|
||
] = None,
|
||
show_secrets: Annotated[
|
||
bool,
|
||
Query(description='是否返回未脱敏的秘密值;默认为 False,启用时仍需通过确认保护。'),
|
||
] = False,
|
||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||
runtime: HostRuntime = Depends(get_host_runtime),
|
||
) -> _SchemaResponse[Any]:
|
||
"""
|
||
按登记元数据查询设置,并默认对敏感值递归脱敏。
|
||
"""
|
||
try:
|
||
data = _settings_service(runtime).query(
|
||
setting_key=setting_key,
|
||
group=group,
|
||
keyword=keyword,
|
||
include_values=include_values,
|
||
show_secrets=show_secrets,
|
||
)
|
||
except ValueError as error:
|
||
return _SchemaResponse(success=False, message=str(error))
|
||
return _SchemaResponse(success=True, data=data)
|
||
|
||
|
||
@router.post(
|
||
"/settings",
|
||
summary='更新单项已登记系统设置',
|
||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||
)
|
||
async def update_settings(
|
||
payload: _SchemaSystemSettingsUpdateRequest,
|
||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||
runtime: HostRuntime = Depends(get_host_runtime),
|
||
) -> _SchemaResponse[Any]:
|
||
"""
|
||
按替换、字典合并或列表项操作更新一个登记设置。
|
||
"""
|
||
try:
|
||
data = await _settings_service(runtime).update(**payload.model_dump())
|
||
except SystemSettingConflictError as error:
|
||
raise HTTPException(status_code=409, detail=str(error)) from error
|
||
except ValueError as error:
|
||
return _SchemaResponse(success=False, message=str(error))
|
||
return _SchemaResponse(success=True, message=data.get("message") or "", data=data)
|