mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
feat: 完善数据库备份管理与版本读取 (#6450)
This commit is contained in:
+113
-5
@@ -23,6 +23,8 @@ from app.schemas.common import TimeData as _SchemaTimeData
|
||||
from app.schemas.common import ValueData as _SchemaValueData
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.schemas.system import NetTestTarget as _SchemaNetTestTarget
|
||||
from app.schemas.system import DatabaseBackupArtifactData as _SchemaDatabaseBackupArtifactData
|
||||
from app.schemas.system import DatabaseBackupVerificationData as _SchemaDatabaseBackupVerificationData
|
||||
from app.schemas.system import PluginMarketSyncData as _SchemaPluginMarketSyncData
|
||||
from app.schemas.system import PluginMarketSyncRequest as _SchemaPluginMarketSyncRequest
|
||||
from app.schemas.system import RuleTestData as _SchemaRuleTestData
|
||||
@@ -46,6 +48,8 @@ from app.application.configuration import (
|
||||
get_configured_system_config,
|
||||
get_runtime_settings,
|
||||
)
|
||||
from app.application.backup import DatabaseBackupInProgressError
|
||||
from app.application.database import get_database_governance
|
||||
from app.application.plugin.runtime import plugin_system_config_mutation
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_superuser,
|
||||
@@ -67,6 +71,7 @@ from app.application.rules import RuleHelper
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.runtime.state import SystemHelper
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.execution import run_in_threadpool_to_completion
|
||||
from app.application.scheduling import get_scheduler
|
||||
from app.schemas.event import ConfigChangeEventData
|
||||
from app.schemas.exception import PluginMutationRejectedError
|
||||
@@ -79,7 +84,7 @@ from app.adapters.system.update import system_update_manager
|
||||
from app.application.security.url import SecurityUtils
|
||||
from app.application.network import NetworkTestService
|
||||
from app.foundation.url import UrlUtils
|
||||
from version import APP_VERSION
|
||||
from app.runtime.version import get_app_version, get_frontend_version
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
@@ -110,6 +115,16 @@ _DATABASE_BACKUP_SETTING_KEYS = {
|
||||
}
|
||||
|
||||
|
||||
def _database_backup_artifact_data(artifact: Any) -> _SchemaDatabaseBackupArtifactData:
|
||||
"""将内部备份制品映射为不含宿主路径的 Web DTO。"""
|
||||
return _SchemaDatabaseBackupArtifactData(
|
||||
name=artifact.name,
|
||||
db_type=artifact.db_type,
|
||||
created_at=artifact.created_at,
|
||||
size=artifact.size,
|
||||
)
|
||||
|
||||
|
||||
def _validate_llm_server_tool_config(env: dict) -> Optional[str]:
|
||||
"""校验强制服务端联网搜索配置,返回用户可读错误信息。"""
|
||||
from app.agent.llm.server_tools import (
|
||||
@@ -759,8 +774,8 @@ def get_global_setting(token: str):
|
||||
# 追加版本信息(用于版本检查)
|
||||
info.update(
|
||||
{
|
||||
"FRONTEND_VERSION": SystemChain.get_frontend_version(),
|
||||
"BACKEND_VERSION": APP_VERSION,
|
||||
"FRONTEND_VERSION": get_frontend_version(),
|
||||
"BACKEND_VERSION": get_app_version(),
|
||||
}
|
||||
)
|
||||
# 仅在后端开发模式下返回该标记,避免生产环境暴露无意义运行态信息
|
||||
@@ -812,6 +827,99 @@ async def get_user_global_setting(_: ApiPrincipal = Depends(get_current_active_u
|
||||
return _SchemaResponse(success=True, data=info)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/database/backups",
|
||||
summary="查询受管数据库备份",
|
||||
response_model=list[_SchemaDatabaseBackupArtifactData],
|
||||
)
|
||||
async def list_database_backups(
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> list[_SchemaDatabaseBackupArtifactData]:
|
||||
"""列出当前备份目录中的正式制品,不触发内容校验。"""
|
||||
try:
|
||||
artifacts = await run_in_threadpool_to_completion(
|
||||
get_database_governance().list_backups
|
||||
)
|
||||
except Exception as error:
|
||||
logger.exception("读取数据库备份列表失败")
|
||||
raise HTTPException(status_code=500, detail="读取数据库备份列表失败,请查看日志") from error
|
||||
return [_database_backup_artifact_data(artifact) for artifact in artifacts]
|
||||
|
||||
|
||||
@router.post(
|
||||
"/database/backups",
|
||||
summary="立即创建数据库备份",
|
||||
response_model=_SchemaDatabaseBackupArtifactData,
|
||||
)
|
||||
async def create_database_backup(
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaDatabaseBackupArtifactData:
|
||||
"""创建、校验并原子发布当前活动数据库的一致快照。"""
|
||||
try:
|
||||
artifact = await run_in_threadpool_to_completion(
|
||||
get_database_governance().create_backup
|
||||
)
|
||||
except DatabaseBackupInProgressError as error:
|
||||
raise HTTPException(status_code=409, detail=str(error)) from error
|
||||
except Exception as error:
|
||||
logger.exception("创建数据库备份失败")
|
||||
raise HTTPException(status_code=500, detail="创建数据库备份失败,请查看日志") from error
|
||||
return _database_backup_artifact_data(artifact)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/database/backups/{name}/verify",
|
||||
summary="校验受管数据库备份",
|
||||
response_model=_SchemaDatabaseBackupVerificationData,
|
||||
)
|
||||
async def verify_database_backup(
|
||||
name: str,
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaDatabaseBackupVerificationData:
|
||||
"""校验一个受管制品,响应不包含宿主路径或适配器错误明细。"""
|
||||
try:
|
||||
verification = await run_in_threadpool_to_completion(
|
||||
get_database_governance().verify_backup,
|
||||
name,
|
||||
)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=400, detail="数据库备份文件名无效") from error
|
||||
except FileNotFoundError as error:
|
||||
raise HTTPException(status_code=404, detail="数据库备份不存在") from error
|
||||
except Exception as error:
|
||||
logger.exception("校验数据库备份失败:%s", name)
|
||||
raise HTTPException(status_code=500, detail="校验数据库备份失败,请查看日志") from error
|
||||
return _SchemaDatabaseBackupVerificationData(
|
||||
valid=verification.valid,
|
||||
method=verification.method,
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/database/backups/{name}",
|
||||
summary="删除受管数据库备份",
|
||||
response_model=_SchemaResponse[None],
|
||||
)
|
||||
async def delete_database_backup(
|
||||
name: str,
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse[None]:
|
||||
"""删除一个受管制品,只接受备份目录内的合法文件名。"""
|
||||
try:
|
||||
await run_in_threadpool_to_completion(
|
||||
get_database_governance().delete_backup,
|
||||
name,
|
||||
)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=400, detail="数据库备份文件名无效") from error
|
||||
except FileNotFoundError as error:
|
||||
raise HTTPException(status_code=404, detail="数据库备份不存在") from error
|
||||
except Exception as error:
|
||||
logger.exception("删除数据库备份失败:%s", name)
|
||||
raise HTTPException(status_code=500, detail="删除数据库备份失败,请查看日志") from error
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/env",
|
||||
summary="查询系统配置",
|
||||
@@ -828,10 +936,10 @@ async def get_env_setting(
|
||||
)
|
||||
info.update(
|
||||
{
|
||||
"VERSION": APP_VERSION,
|
||||
"VERSION": get_app_version(),
|
||||
"AUTH_VERSION": SitesHelper().auth_version,
|
||||
"INDEXER_VERSION": SitesHelper().indexer_version,
|
||||
"FRONTEND_VERSION": SystemChain().get_frontend_version(),
|
||||
"FRONTEND_VERSION": get_frontend_version(),
|
||||
"RUST_ACCEL": rust_accel.is_config_enabled(),
|
||||
"RUST_ACCEL_AVAILABLE": rust_accel.is_available(),
|
||||
"RUST_ACCEL_ENABLED": rust_accel.is_enabled(),
|
||||
|
||||
Reference in New Issue
Block a user