mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor: 收口 V3 分层架构与插件兼容边界
This commit is contained in:
+72
-71
@@ -16,7 +16,6 @@ from typing import Any, AsyncIterator, Callable, Optional, Union
|
||||
from fastapi import Depends, File, Form, HTTPException, Request, UploadFile, status
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.schemas.agent import AgentChatDisplaySaveRequest as _SchemaAgentChatDisplaySaveRequest
|
||||
from app.schemas.agent import AgentChatSessionDetail as _SchemaAgentChatSessionDetail
|
||||
@@ -45,12 +44,14 @@ from app.chain.message import MessageChain
|
||||
from app.command import Command
|
||||
from app.runtime.config import global_vars, settings
|
||||
from app.runtime.events import Event, EventManager
|
||||
from app.db import get_async_db
|
||||
from app.db.oper.agentchat import AgentChatOper
|
||||
from app.db.models import User
|
||||
from app.db.models.agentchat import AgentChat
|
||||
from app.db.oper.user import UserOper
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.api.deps import get_agent_chat_service, get_current_active_user
|
||||
from app.application.messaging.chat import (
|
||||
AgentChatRecord,
|
||||
AgentChatService,
|
||||
get_configured_agent_chat_service,
|
||||
)
|
||||
from app.application.security.user import get_configured_user_id_lookup
|
||||
from app.application.messaging.agent import attach_web_agent_edit_queue, detach_web_agent_edit_queue
|
||||
from app.application.messaging.agent import agent_interaction_manager
|
||||
from app.application.messaging.agent import (
|
||||
@@ -187,7 +188,7 @@ class _WebAgentEventPublisher:
|
||||
self._pending_signal.clear()
|
||||
|
||||
|
||||
def _ensure_superuser(user: User) -> None:
|
||||
def _ensure_superuser(user: ApiPrincipal) -> None:
|
||||
"""校验当前用户是否为超级管理员。"""
|
||||
if not getattr(user, "is_superuser", False):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
|
||||
@@ -199,7 +200,7 @@ def _ensure_superuser(user: User) -> None:
|
||||
response_model=_SchemaResponse[_SchemaAgentMcpServerListData],
|
||||
)
|
||||
async def list_agent_mcp_servers(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
查询 Agent 外部 MCP 服务器配置。
|
||||
@@ -224,7 +225,7 @@ async def list_agent_mcp_servers(
|
||||
)
|
||||
async def save_agent_mcp_servers(
|
||||
request: _SchemaAgentMcpServersSaveRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
保存 Agent 外部 MCP 服务器配置。
|
||||
@@ -244,7 +245,7 @@ async def save_agent_mcp_servers(
|
||||
)
|
||||
async def test_agent_mcp_server(
|
||||
request: _SchemaAgentMcpServerTestRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
测试 Agent 外部 MCP 服务器连接并读取工具列表。
|
||||
@@ -431,7 +432,7 @@ class _WebAgentMoviePilotAgentMixin:
|
||||
if not self.user_id:
|
||||
return False
|
||||
try:
|
||||
user = await UserOper().async_get_by_id(int(self.user_id))
|
||||
user = get_configured_user_id_lookup()(int(self.user_id))
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
except Exception as e:
|
||||
@@ -486,7 +487,7 @@ def _get_web_agent_type() -> type:
|
||||
return _WEB_AGENT_TYPE
|
||||
|
||||
|
||||
def _build_web_agent_session_id(user: User, session_id: Optional[str]) -> str:
|
||||
def _build_web_agent_session_id(user: ApiPrincipal, session_id: Optional[str]) -> str:
|
||||
"""
|
||||
构建前端 Agent 会话 ID。
|
||||
|
||||
@@ -498,8 +499,8 @@ def _build_web_agent_session_id(user: User, session_id: Optional[str]) -> str:
|
||||
if seed.startswith(WEB_AGENT_SESSION_PREFIX):
|
||||
return seed
|
||||
try:
|
||||
existing_chat = AgentChatOper().get(session_id=seed)
|
||||
if existing_chat and _can_access_agent_chat(existing_chat, user):
|
||||
existing_chat = get_configured_agent_chat_service().get_sync(seed)
|
||||
if existing_chat and AgentChatService.can_access(existing_chat, user):
|
||||
return seed
|
||||
except Exception as e:
|
||||
logger.debug(f"读取WebAgent历史会话失败: {e}")
|
||||
@@ -508,7 +509,7 @@ def _build_web_agent_session_id(user: User, session_id: Optional[str]) -> str:
|
||||
return f"{WEB_AGENT_SESSION_PREFIX}{digest[:32]}"
|
||||
|
||||
|
||||
def _can_access_agent_chat(chat: AgentChat, user: User) -> bool:
|
||||
def _can_access_agent_chat(chat: Any, user: ApiPrincipal) -> bool:
|
||||
"""
|
||||
判断当前登录用户是否可以访问指定 Agent 会话。
|
||||
|
||||
@@ -524,15 +525,14 @@ def _can_access_agent_chat(chat: AgentChat, user: User) -> bool:
|
||||
|
||||
|
||||
async def _get_accessible_agent_chat(
|
||||
oper: AgentChatOper, session_id: str, user: User
|
||||
) -> Optional[AgentChat]:
|
||||
service: AgentChatService,
|
||||
session_id: str,
|
||||
user: ApiPrincipal,
|
||||
) -> Optional[AgentChatRecord]:
|
||||
"""
|
||||
读取当前用户可访问的 Agent 会话。
|
||||
"""
|
||||
chat = await oper.async_get(session_id=session_id)
|
||||
if not chat or not _can_access_agent_chat(chat, user):
|
||||
return None
|
||||
return chat
|
||||
return await service.get_accessible(session_id, user)
|
||||
|
||||
|
||||
def _append_web_agent_text_segment(assistant_message: dict, content: str) -> None:
|
||||
@@ -631,7 +631,7 @@ def _apply_web_agent_display_event(event: dict, assistant_message: dict) -> None
|
||||
def _save_web_agent_display_snapshot(
|
||||
*,
|
||||
session_id: str,
|
||||
current_user: User,
|
||||
current_user: ApiPrincipal,
|
||||
messages: list[dict],
|
||||
client_session_id: Optional[str] = None,
|
||||
) -> None:
|
||||
@@ -639,9 +639,9 @@ def _save_web_agent_display_snapshot(
|
||||
保存 WebAgent 当前展示消息快照。
|
||||
"""
|
||||
try:
|
||||
oper = AgentChatOper()
|
||||
existing_chat = oper.get(session_id=session_id)
|
||||
AgentChatOper().save_display_messages(
|
||||
service = get_configured_agent_chat_service()
|
||||
existing_chat = service.get_sync(session_id)
|
||||
service.save_display_sync(
|
||||
session_id=session_id,
|
||||
user_id=(existing_chat.user_id if existing_chat else str(current_user.id)),
|
||||
username=(existing_chat.username if existing_chat else current_user.name),
|
||||
@@ -716,7 +716,7 @@ def _sanitize_web_agent_upload_name(
|
||||
return safe_name
|
||||
|
||||
|
||||
def _get_web_agent_upload_dir(user: User, session_id: Optional[str]) -> Path:
|
||||
def _get_web_agent_upload_dir(user: ApiPrincipal, session_id: Optional[str]) -> Path:
|
||||
"""
|
||||
计算当前 Web Agent 会话的临时附件目录。
|
||||
|
||||
@@ -1425,7 +1425,7 @@ def _get_web_agent_unknown_command_message(text: str) -> Optional[str]:
|
||||
return f"命令不存在:{command}"
|
||||
|
||||
|
||||
def _ensure_web_agent_command_allowed(current_user: User) -> Optional[str]:
|
||||
def _ensure_web_agent_command_allowed(current_user: ApiPrincipal) -> Optional[str]:
|
||||
"""
|
||||
校验当前 Web 用户是否可以执行传统斜杠命令。
|
||||
|
||||
@@ -1440,7 +1440,7 @@ def _ensure_web_agent_command_allowed(current_user: User) -> Optional[str]:
|
||||
async def _collect_web_agent_traditional_events(
|
||||
*,
|
||||
text: str,
|
||||
current_user: User,
|
||||
current_user: ApiPrincipal,
|
||||
original_message_id: Optional[Union[str, int]] = None,
|
||||
original_chat_id: Optional[Union[str, int]] = None,
|
||||
) -> list[dict]:
|
||||
@@ -1637,7 +1637,7 @@ async def download_web_agent_file(file_id: str) -> FileResponse:
|
||||
async def upload_web_agent_file(
|
||||
file: UploadFile = File(...),
|
||||
session_id: Optional[str] = Form(None),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
上传 Web 智能助手对话附件。
|
||||
@@ -1680,7 +1680,7 @@ async def upload_web_agent_file(
|
||||
)
|
||||
async def web_agent_callback(
|
||||
payload: _SchemaAgentWebChoiceRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
接收 Web 智能助手选择卡片回调。
|
||||
@@ -1717,7 +1717,7 @@ async def web_agent_callback(
|
||||
response_model=_SchemaResponse[list[_SchemaAgentWebCommandInfo]],
|
||||
)
|
||||
async def list_web_agent_commands(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
获取当前 Web 智能助手可补全的斜杠命令。
|
||||
@@ -1737,8 +1737,8 @@ async def list_web_agent_commands(
|
||||
response_model=_SchemaResponse[list[_SchemaAgentChatSessionSummary]],
|
||||
)
|
||||
async def list_agent_chat_sessions(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
service: AgentChatService = Depends(get_agent_chat_service),
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 30,
|
||||
) -> _SchemaResponse:
|
||||
@@ -1746,23 +1746,17 @@ async def list_agent_chat_sessions(
|
||||
获取当前用户可访问的 Agent 历史会话列表。
|
||||
|
||||
:param current_user: 当前登录用户
|
||||
:param db: 异步数据库会话
|
||||
:param service: Agent 会话应用服务
|
||||
:param page: 页码
|
||||
:param count: 每页数量
|
||||
:return: 会话摘要列表
|
||||
"""
|
||||
user_id = None if current_user.is_superuser else str(current_user.id)
|
||||
username = None if current_user.is_superuser else current_user.name
|
||||
chats = await AgentChatOper(db).async_list_by_page(
|
||||
chats = await service.list(
|
||||
current_user,
|
||||
page=page,
|
||||
count=count,
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
)
|
||||
return _SchemaResponse(
|
||||
success=True,
|
||||
data=[AgentChatOper.to_summary(chat) for chat in chats],
|
||||
)
|
||||
return _SchemaResponse(success=True, data=chats)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -1772,24 +1766,27 @@ async def list_agent_chat_sessions(
|
||||
)
|
||||
async def get_agent_chat_session(
|
||||
session_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
service: AgentChatService = Depends(get_agent_chat_service),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
获取一条 Agent 历史会话详情。
|
||||
|
||||
:param session_id: Agent 会话 ID
|
||||
:param current_user: 当前登录用户
|
||||
:param db: 异步数据库会话
|
||||
:param service: Agent 会话应用服务
|
||||
:return: 会话详情
|
||||
"""
|
||||
oper = AgentChatOper(db)
|
||||
chat = await _get_accessible_agent_chat(oper, session_id, current_user)
|
||||
chat = await _get_accessible_agent_chat(service, session_id, current_user)
|
||||
server_session_id = session_id
|
||||
if not chat:
|
||||
server_session_id = _build_web_agent_session_id(current_user, session_id)
|
||||
if server_session_id != session_id:
|
||||
chat = await _get_accessible_agent_chat(oper, server_session_id, current_user)
|
||||
chat = await _get_accessible_agent_chat(
|
||||
service,
|
||||
server_session_id,
|
||||
current_user,
|
||||
)
|
||||
if not chat:
|
||||
manager = get_running_agent_manager()
|
||||
if manager and manager.is_session_busy(server_session_id):
|
||||
@@ -1803,7 +1800,7 @@ async def get_agent_chat_session(
|
||||
},
|
||||
)
|
||||
return _SchemaResponse(success=False, message="会话不存在或无权访问")
|
||||
data = AgentChatOper.to_detail(chat)
|
||||
data = service.to_detail(chat).model_dump()
|
||||
manager = get_running_agent_manager()
|
||||
data["is_processing"] = bool(
|
||||
manager and manager.is_session_busy(chat.session_id)
|
||||
@@ -1819,8 +1816,8 @@ async def get_agent_chat_session(
|
||||
async def save_agent_chat_display(
|
||||
session_id: str,
|
||||
payload: _SchemaAgentChatDisplaySaveRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
service: AgentChatService = Depends(get_agent_chat_service),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
保存前端聚合后的 Agent 展示消息。
|
||||
@@ -1828,12 +1825,15 @@ async def save_agent_chat_display(
|
||||
:param session_id: Agent 会话 ID
|
||||
:param payload: 展示消息保存请求
|
||||
:param current_user: 当前登录用户
|
||||
:param db: 异步数据库会话
|
||||
:param service: Agent 会话应用服务
|
||||
:return: 保存后的会话摘要
|
||||
"""
|
||||
oper = AgentChatOper(db)
|
||||
existing_chat = await oper.async_get(session_id=session_id)
|
||||
if existing_chat and not _can_access_agent_chat(existing_chat, current_user):
|
||||
existing_chat = await service.get_accessible(session_id, current_user)
|
||||
if existing_chat is None:
|
||||
unrestricted_chat = await service.get(session_id)
|
||||
else:
|
||||
unrestricted_chat = existing_chat
|
||||
if unrestricted_chat and existing_chat is None:
|
||||
return _SchemaResponse(success=False, message="会话不存在或无权访问")
|
||||
|
||||
messages = [
|
||||
@@ -1847,10 +1847,10 @@ async def save_agent_chat_display(
|
||||
messages=messages,
|
||||
client_session_id=existing_chat.client_session_id if existing_chat else session_id,
|
||||
)
|
||||
chat = await oper.async_get(session_id=session_id)
|
||||
chat = await service.get_accessible(session_id, current_user)
|
||||
if not chat:
|
||||
return _SchemaResponse(success=False, message="会话保存失败")
|
||||
return _SchemaResponse(success=True, data=AgentChatOper.to_summary(chat))
|
||||
return _SchemaResponse(success=True, data=service.to_summary(chat))
|
||||
|
||||
|
||||
@router.delete(
|
||||
@@ -1860,22 +1860,21 @@ async def save_agent_chat_display(
|
||||
)
|
||||
async def delete_agent_chat_session(
|
||||
session_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
service: AgentChatService = Depends(get_agent_chat_service),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
删除一条 Agent 历史会话。
|
||||
|
||||
:param session_id: Agent 会话 ID
|
||||
:param current_user: 当前登录用户
|
||||
:param db: 异步数据库会话
|
||||
:param service: Agent 会话应用服务
|
||||
:return: 删除结果
|
||||
"""
|
||||
oper = AgentChatOper(db)
|
||||
chat = await _get_accessible_agent_chat(oper, session_id, current_user)
|
||||
chat = await _get_accessible_agent_chat(service, session_id, current_user)
|
||||
if not chat:
|
||||
return _SchemaResponse(success=False, message="会话不存在或无权访问")
|
||||
deleted = await oper.async_delete(session_id=session_id)
|
||||
deleted = await service.delete(session_id, current_user)
|
||||
return _SchemaResponse(success=deleted, message="删除成功" if deleted else "删除失败")
|
||||
|
||||
|
||||
@@ -1886,23 +1885,25 @@ async def delete_agent_chat_session(
|
||||
)
|
||||
async def stop_web_agent_session_task(
|
||||
session_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
service: AgentChatService = Depends(get_agent_chat_service),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
停止当前 Web 智能助手会话正在执行的任务。
|
||||
|
||||
:param session_id: Agent 会话 ID
|
||||
:param current_user: 当前登录用户
|
||||
:param db: 异步数据库会话
|
||||
:param service: Agent 会话应用服务
|
||||
:return: 停止结果
|
||||
"""
|
||||
server_session_id = _build_web_agent_session_id(current_user, session_id)
|
||||
chat = await _get_accessible_agent_chat(
|
||||
AgentChatOper(db), server_session_id, current_user
|
||||
service,
|
||||
server_session_id,
|
||||
current_user,
|
||||
)
|
||||
if not chat and server_session_id != session_id:
|
||||
chat = await _get_accessible_agent_chat(AgentChatOper(db), session_id, current_user)
|
||||
chat = await _get_accessible_agent_chat(service, session_id, current_user)
|
||||
if chat and not _can_access_agent_chat(chat, current_user):
|
||||
return _SchemaResponse(success=False, message="会话不存在或无权访问")
|
||||
|
||||
@@ -1930,7 +1931,7 @@ async def stop_web_agent_session_task(
|
||||
async def web_agent_stream(
|
||||
payload: _SchemaAgentWebChatRequest,
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Web 智能助手流式对话。
|
||||
|
||||
@@ -8,7 +8,7 @@ from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.anilist import AniListChain
|
||||
from app.domain.context import MediaInfo
|
||||
from app.application.security.access import verify_token
|
||||
from app.adapters.web.security.access import verify_token
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from app.api.openai_utils import (
|
||||
)
|
||||
from app.agent.runtime_loader import get_running_agent_manager
|
||||
from app.runtime.config import settings
|
||||
from app.application.security.access import anthropic_api_key_header
|
||||
from app.adapters.web.security.access import anthropic_api_key_header
|
||||
|
||||
ANTHROPIC_ERROR_RESPONSES = {
|
||||
400: {"model": _SchemaAnthropicErrorResponse, "description": "请求格式错误"},
|
||||
|
||||
+14
-12
@@ -1,15 +1,14 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi import Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.schemas.token import Token as _SchemaToken
|
||||
from app.schemas.user import AuthProviderInfo as _SchemaAuthProviderInfo
|
||||
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
|
||||
from app.application.security.auth import build_token_response, consume_plugin_auth_ticket
|
||||
from app.runtime.extensions.plugin_manager import PluginManager
|
||||
from app.db.models.passkey import PassKey
|
||||
from app.db.models.user import User
|
||||
from app.application.security.auth import AuthService, consume_plugin_auth_ticket
|
||||
from app.application.plugin.runtime import get_plugin_manager as PluginManager
|
||||
from app.api.deps import get_auth_service
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
@@ -22,13 +21,13 @@ class AuthExchangeRequest(BaseModel):
|
||||
ticket: str
|
||||
|
||||
|
||||
def _system_auth_providers() -> list[dict[str, Any]]:
|
||||
def _system_auth_providers(service: AuthService) -> list[dict[str, Any]]:
|
||||
"""
|
||||
获取系统内建的匿名登录方式摘要。
|
||||
|
||||
:return: 系统认证提供方列表
|
||||
"""
|
||||
has_passkey = bool(PassKey.list(db=None))
|
||||
has_passkey = service.has_passkey()
|
||||
return [
|
||||
{
|
||||
"id": "system:passkey",
|
||||
@@ -46,13 +45,13 @@ def _system_auth_providers() -> list[dict[str, Any]]:
|
||||
summary="查询登录认证提供方",
|
||||
response_model=list[_SchemaAuthProviderInfo],
|
||||
)
|
||||
def auth_providers() -> list[dict[str, Any]]:
|
||||
def auth_providers(service: AuthService = Depends(get_auth_service)) -> list[dict[str, Any]]:
|
||||
"""
|
||||
查询系统和插件提供的登录认证入口。
|
||||
|
||||
:return: 认证提供方摘要列表
|
||||
"""
|
||||
providers = _system_auth_providers()
|
||||
providers = _system_auth_providers(service)
|
||||
providers.extend(PluginManager().get_plugin_auth_providers())
|
||||
return [provider for provider in providers if provider.get("enabled", True)]
|
||||
|
||||
@@ -63,7 +62,10 @@ def auth_providers() -> list[dict[str, Any]]:
|
||||
response_model=_SchemaToken,
|
||||
openapi_extra={RAW_RESPONSE_OPENAPI_KEY: True},
|
||||
)
|
||||
def auth_exchange(body: AuthExchangeRequest) -> _SchemaToken:
|
||||
def auth_exchange(
|
||||
body: AuthExchangeRequest,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> _SchemaToken:
|
||||
"""
|
||||
将插件认证成功后生成的一次性票据兑换为系统 Token。
|
||||
|
||||
@@ -74,8 +76,8 @@ def auth_exchange(body: AuthExchangeRequest) -> _SchemaToken:
|
||||
if not ticket_data:
|
||||
raise HTTPException(status_code=401, detail="认证票据无效或已过期")
|
||||
|
||||
user = User.get(db=None, rid=ticket_data.get("user_id"))
|
||||
user = service.get_user_by_id(ticket_data.get("user_id"))
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户不存在或已禁用")
|
||||
|
||||
return build_token_response(user)
|
||||
return service.build_token_response(user)
|
||||
|
||||
@@ -8,7 +8,7 @@ from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.bangumi import BangumiChain
|
||||
from app.domain.context import MediaInfo
|
||||
from app.application.security.access import verify_token
|
||||
from app.adapters.web.security.access import verify_token
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ from pathlib import Path
|
||||
from typing import Any, List, Optional, Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.dashboard import DashboardMemoryInfo as _SchemaDashboardMemoryInfo
|
||||
from app.schemas.dashboard import DashboardSystemInfo as _SchemaDashboardSystemInfo
|
||||
@@ -17,53 +16,17 @@ from app.api.response import ResponseAPIRouter
|
||||
from app.chain.dashboard import DashboardChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.config import settings
|
||||
from app.application.security.access import verify_apitoken
|
||||
from app.db import get_db
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.api.deps import get_current_active_superuser
|
||||
from app.adapters.web.security.access import verify_apitoken
|
||||
from app.api.deps import get_current_active_superuser, get_dashboard_query_service
|
||||
from app.application.dashboard import DashboardQueryService
|
||||
from app.schemas.types import StorageAction
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.scheduler import Scheduler
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.adapters.system.host import SystemUtils
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
def _build_statistic(db: Session, name: Optional[str] = None) -> _SchemaStatistic:
|
||||
"""
|
||||
构建媒体数量统计信息。
|
||||
"""
|
||||
media_statistics: Optional[List[_SchemaStatistic]] = (
|
||||
DashboardChain().media_statistic(name)
|
||||
)
|
||||
if media_statistics:
|
||||
# 汇总各媒体库统计信息
|
||||
ret_statistic = _SchemaStatistic()
|
||||
has_episode_count = False
|
||||
for media_statistic in media_statistics:
|
||||
ret_statistic.movie_count += media_statistic.movie_count or 0
|
||||
ret_statistic.tv_count += media_statistic.tv_count or 0
|
||||
ret_statistic.music_count += media_statistic.music_count or 0
|
||||
ret_statistic.user_count += media_statistic.user_count or 0
|
||||
if media_statistic.episode_count is not None:
|
||||
ret_statistic.episode_count += media_statistic.episode_count or 0
|
||||
has_episode_count = True
|
||||
if not has_episode_count:
|
||||
# 所有媒体服务都未提供剧集统计时,返回 None 供前端展示“未获取”。
|
||||
ret_statistic.episode_count = None
|
||||
else:
|
||||
ret_statistic = _SchemaStatistic()
|
||||
|
||||
movie_count_month, tv_count_month, episode_count_month, music_count_month = (
|
||||
TransferHistory.monthly_media_statistics(db)
|
||||
)
|
||||
ret_statistic.movie_count_month = movie_count_month
|
||||
ret_statistic.tv_count_month = tv_count_month
|
||||
ret_statistic.episode_count_month = episode_count_month
|
||||
ret_statistic.music_count_month = music_count_month
|
||||
return ret_statistic
|
||||
|
||||
|
||||
def _build_storage() -> _SchemaStorage:
|
||||
"""
|
||||
构建本地存储空间信息。
|
||||
@@ -114,13 +77,13 @@ def _build_downloader(name: Optional[str] = None) -> _SchemaDownloaderInfo:
|
||||
@router.get("/statistic", summary="媒体数量统计", response_model=_SchemaStatistic)
|
||||
def statistic(
|
||||
name: Optional[str] = None,
|
||||
db: Session = Depends(get_db),
|
||||
service: DashboardQueryService = Depends(get_dashboard_query_service),
|
||||
_: Any = Depends(get_current_active_superuser),
|
||||
) -> Any:
|
||||
"""
|
||||
查询媒体数量统计信息
|
||||
"""
|
||||
return _build_statistic(db, name)
|
||||
return service.statistic(name)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -128,12 +91,12 @@ def statistic(
|
||||
)
|
||||
def statistic2(
|
||||
_: Annotated[str, Depends(verify_apitoken)],
|
||||
db: Session = Depends(get_db),
|
||||
service: DashboardQueryService = Depends(get_dashboard_query_service),
|
||||
) -> Any:
|
||||
"""
|
||||
查询媒体数量统计信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
return _build_statistic(db)
|
||||
return service.statistic()
|
||||
|
||||
|
||||
@router.get("/storage", summary="本地存储空间", response_model=_SchemaStorage)
|
||||
@@ -249,14 +212,13 @@ async def schedule_progress2(
|
||||
@router.get("/transfer", summary="文件整理统计", response_model=List[int])
|
||||
async def transfer(
|
||||
days: Optional[int] = 7,
|
||||
db: Session = Depends(get_db),
|
||||
service: DashboardQueryService = Depends(get_dashboard_query_service),
|
||||
_: Any = Depends(get_current_active_superuser),
|
||||
) -> Any:
|
||||
"""
|
||||
查询文件整理统计信息
|
||||
"""
|
||||
transfer_stat = await TransferHistory.async_statistic(db, days)
|
||||
return [stat[1] for stat in transfer_stat]
|
||||
return await service.transfer(days)
|
||||
|
||||
|
||||
@router.get("/cpu", summary="获取当前CPU使用率", response_model=float)
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.chain.bangumi import BangumiChain
|
||||
from app.chain.douban import DoubanChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.runtime.events import eventmanager
|
||||
from app.application.security.access import verify_token
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.schemas.event import DiscoverSourceEventData
|
||||
from app.schemas.types import ChainEventType, MediaType
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.douban import DoubanChain
|
||||
from app.domain.context import MediaInfo
|
||||
from app.application.security.access import verify_token
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
@@ -20,11 +20,14 @@ from app.chain.media import MediaChain
|
||||
from app.domain.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.application.security.access import verify_token
|
||||
from app.db.models.user import User
|
||||
from app.db.oper.site import SiteOper
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.site.query import (
|
||||
SiteQueryService,
|
||||
get_configured_site_query_service,
|
||||
)
|
||||
from app.api.deps import get_current_active_user, get_site_sync_query_service
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
@@ -39,7 +42,10 @@ from app.application.security.url import SecurityUtils
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
def _prepare_subtitle_download(subtitle: SubtitleInfo) -> tuple[bool, str]:
|
||||
def _prepare_subtitle_download(
|
||||
subtitle: SubtitleInfo,
|
||||
query: SiteQueryService | None = None,
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
校验字幕下载签名,并用服务端站点配置覆盖请求凭据。
|
||||
"""
|
||||
@@ -53,7 +59,8 @@ def _prepare_subtitle_download(subtitle: SubtitleInfo) -> tuple[bool, str]:
|
||||
if not clean_url:
|
||||
return False, "字幕下载链接签名无效"
|
||||
|
||||
site = SiteOper().get(subtitle.site)
|
||||
site_query = query or get_configured_site_query_service()
|
||||
site = site_query.get_sync(subtitle.site)
|
||||
if not site:
|
||||
return False, "字幕站点信息不存在"
|
||||
|
||||
@@ -84,7 +91,7 @@ def download(
|
||||
torrent_in: _SchemaTorrentInfo,
|
||||
downloader: Annotated[str | None, Body()] = None,
|
||||
save_path: Annotated[str | None, Body()] = None,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
添加下载任务(含媒体信息)
|
||||
@@ -130,7 +137,7 @@ def add(
|
||||
downloader: Annotated[str | None, Body()] = None,
|
||||
# 保存路径, 支持<storage>:<path>, 如rclone:/MP, smb:/server/share/Movies等
|
||||
save_path: Annotated[str | None, Body()] = None,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
添加下载任务(不含媒体信息)
|
||||
@@ -213,14 +220,20 @@ def download_subtitle(
|
||||
media_source: Annotated[MediaSource, Body()],
|
||||
media_id: Annotated[str, Body()],
|
||||
save_path: Annotated[str | None, Body()] = None,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
query: SiteQueryService = Depends(get_site_sync_query_service),
|
||||
) -> Any:
|
||||
"""
|
||||
下载字幕资源。
|
||||
"""
|
||||
subtitle_info = SubtitleInfo()
|
||||
subtitle_info.from_dict(subtitle_in.model_dump())
|
||||
valid, message = _prepare_subtitle_download(subtitle_info)
|
||||
# 直接调用 endpoint 的旧测试/插件入口不会经过 FastAPI 依赖解析;此时让
|
||||
# 应用查询端口自行提供服务,仍保留真实请求中的注入对象。
|
||||
if not hasattr(query, "get_sync"):
|
||||
valid, message = _prepare_subtitle_download(subtitle_info)
|
||||
else:
|
||||
valid, message = _prepare_subtitle_download(subtitle_info, query)
|
||||
if not valid:
|
||||
return _SchemaResponse(success=False, message=message)
|
||||
|
||||
@@ -273,7 +286,7 @@ async def clients(_: _SchemaTokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询可用下载器
|
||||
"""
|
||||
downloaders: List[dict] = SystemConfigOper().get(SystemConfigKey.Downloaders)
|
||||
downloaders: List[dict] = get_configured_system_config().get(SystemConfigKey.Downloaders)
|
||||
if downloaders:
|
||||
return [
|
||||
{"name": d.get("name"), "type": d.get("type")}
|
||||
|
||||
@@ -3,8 +3,6 @@ import time
|
||||
from typing import List, Any, Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.common import BatchProgressKeyData as _SchemaBatchProgressKeyData
|
||||
from app.schemas.common import ProgressKeyData as _SchemaProgressKeyData
|
||||
@@ -22,23 +20,20 @@ from app.agent.prompt.transfer_redo import (
|
||||
build_manual_redo_prompt,
|
||||
)
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.application.security.access import verify_token
|
||||
from app.db import get_async_db, get_db
|
||||
from app.db.models import User
|
||||
from app.db.models.downloadhistory import DownloadHistory
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.deps import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_superuser,
|
||||
get_download_history_mutation_command,
|
||||
get_history_query_service,
|
||||
get_transfer_history_mutation_command,
|
||||
)
|
||||
from app.runtime.progress import ProgressHelper
|
||||
from app.application.history import (
|
||||
DownloadHistoryMutationCommand,
|
||||
HistoryQueryService,
|
||||
TransferHistoryMutationCommand,
|
||||
)
|
||||
from app.foundation.text import cut as jieba_cut
|
||||
from app.runtime.log import logger
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
@@ -155,13 +150,13 @@ def _start_batch_ai_redo_task(
|
||||
async def download_history(
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 30,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: HistoryQueryService = Depends(get_history_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
按下载时间倒序查询下载历史记录
|
||||
"""
|
||||
return await DownloadHistory.async_list_by_page(db, page, count)
|
||||
return await query.list_download(page=page, count=count)
|
||||
|
||||
|
||||
@router.delete(
|
||||
@@ -183,14 +178,6 @@ def delete_download_history(
|
||||
return _SchemaResponse(success=result.success, message=result.message)
|
||||
|
||||
|
||||
def _glob_to_like(pattern: str) -> str:
|
||||
"""
|
||||
将 glob 通配符模式转换为 SQL LIKE 模式(使用 \\ 作为转义字符)
|
||||
"""
|
||||
result = pattern.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
||||
return result.replace("*", "%").replace("?", "_")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/transfer",
|
||||
summary="查询整理记录",
|
||||
@@ -201,50 +188,19 @@ async def transfer_history(
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 30,
|
||||
status: Optional[bool] = None,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: HistoryQueryService = Depends(get_history_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
查询整理记录,title 支持通配符 * 和 ?(如 *.mkv、*2024*)
|
||||
"""
|
||||
if title == "失败":
|
||||
title = None
|
||||
status = False
|
||||
elif title == "成功":
|
||||
title = None
|
||||
status = True
|
||||
|
||||
if title:
|
||||
if "*" in title or "?" in title:
|
||||
like_pattern = _glob_to_like(title)
|
||||
total = await TransferHistory.async_count_by_title(
|
||||
db, title=like_pattern, status=status, wildcard=True
|
||||
)
|
||||
result = await TransferHistory.async_list_by_title(
|
||||
db, title=like_pattern, page=page, count=count, status=status, wildcard=True
|
||||
)
|
||||
else:
|
||||
words = jieba_cut(title, HMM=False)
|
||||
like_pattern = "%".join(words)
|
||||
total = await TransferHistory.async_count_by_title(
|
||||
db, title=like_pattern, status=status
|
||||
)
|
||||
result = await TransferHistory.async_list_by_title(
|
||||
db, title=like_pattern, page=page, count=count, status=status
|
||||
)
|
||||
else:
|
||||
result = await TransferHistory.async_list_by_page(
|
||||
db, page=page, count=count, status=status
|
||||
)
|
||||
total = await TransferHistory.async_count(db, status=status)
|
||||
|
||||
return _SchemaResponse(
|
||||
success=True,
|
||||
data={
|
||||
"list": [item.to_dict() for item in result],
|
||||
"total": total,
|
||||
},
|
||||
result = await query.list_transfer(
|
||||
title=title,
|
||||
page=page,
|
||||
count=count,
|
||||
status=status,
|
||||
)
|
||||
return _SchemaResponse(success=True, data=result)
|
||||
|
||||
|
||||
@router.delete("/transfer", summary="删除整理记录", response_model=_SchemaResponse[None])
|
||||
@@ -255,7 +211,7 @@ def delete_transfer_history(
|
||||
command: TransferHistoryMutationCommand = Depends(
|
||||
get_transfer_history_mutation_command
|
||||
),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
删除整理记录。
|
||||
@@ -273,10 +229,10 @@ def delete_transfer_history(
|
||||
summary="智能助手重新整理",
|
||||
response_model=_SchemaResponse[_SchemaProgressKeyData],
|
||||
)
|
||||
def ai_redo_transfer_history(
|
||||
async def ai_redo_transfer_history(
|
||||
history_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
query: HistoryQueryService = Depends(get_history_query_service),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
手动触发单条历史记录的 AI 重新整理,并返回进度键。
|
||||
@@ -284,7 +240,7 @@ def ai_redo_transfer_history(
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
return _SchemaResponse(success=False, message="MoviePilot智能助手未启用")
|
||||
|
||||
history = TransferHistory.get(db, history_id)
|
||||
history = await query.get_transfer(history_id)
|
||||
if not history:
|
||||
return _SchemaResponse(success=False, message="整理记录不存在")
|
||||
|
||||
@@ -304,10 +260,10 @@ def ai_redo_transfer_history(
|
||||
summary="智能助手批量重新整理",
|
||||
response_model=_SchemaResponse[_SchemaBatchProgressKeyData],
|
||||
)
|
||||
def batch_ai_redo_transfer_history(
|
||||
async def batch_ai_redo_transfer_history(
|
||||
payload: _SchemaBatchTransferHistoryRedoRequest,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
query: HistoryQueryService = Depends(get_history_query_service),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
手动触发多条历史记录的 AI 批量重新整理,并返回进度键。
|
||||
@@ -319,14 +275,7 @@ def batch_ai_redo_transfer_history(
|
||||
if not history_ids:
|
||||
return _SchemaResponse(success=False, message="未提供有效的整理记录")
|
||||
|
||||
histories = []
|
||||
missing_ids = []
|
||||
for history_id in history_ids:
|
||||
history = TransferHistory.get(db, history_id)
|
||||
if not history:
|
||||
missing_ids.append(history_id)
|
||||
continue
|
||||
histories.append(history)
|
||||
histories, missing_ids = await query.get_transfers(history_ids)
|
||||
|
||||
if missing_ids:
|
||||
return _SchemaResponse(
|
||||
@@ -358,7 +307,7 @@ def empty_transfer_history(
|
||||
command: TransferHistoryMutationCommand = Depends(
|
||||
get_transfer_history_mutation_command
|
||||
),
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: object = Depends(get_current_active_superuser),
|
||||
) -> Any:
|
||||
"""
|
||||
清空整理记录
|
||||
|
||||
@@ -6,7 +6,6 @@ from fastapi.responses import HTMLResponse
|
||||
from app.schemas.common import ManageRequest as _SchemaManageRequest
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.db.models import User
|
||||
from app.api.deps import get_current_active_superuser_async
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
@@ -29,7 +28,7 @@ def _get_llm_provider_manager_type() -> type:
|
||||
async def manage_provider(
|
||||
request: Request,
|
||||
payload: _SchemaManageRequest,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
):
|
||||
"""
|
||||
LLM 提供商统一管理入口:前端上送 target/action/params 原样透传,
|
||||
|
||||
@@ -11,9 +11,10 @@ from app.schemas.token import Token as _SchemaToken
|
||||
from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
|
||||
from app.chain.user import MfaRequired, UserChain
|
||||
from app.application.security import access as security
|
||||
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
|
||||
from app.application.security.token import create_access_token
|
||||
from app.runtime.config import settings
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from app.application.image import WallpaperHelper
|
||||
from app.schemas.types import SystemConfigKey
|
||||
@@ -67,17 +68,17 @@ def login_access_token(
|
||||
level = SitesHelper().auth_level
|
||||
# 是否显示配置向导
|
||||
show_wizard = (
|
||||
not SystemConfigOper().get(SystemConfigKey.SetupWizardState)
|
||||
not get_configured_system_config().get(SystemConfigKey.SetupWizardState)
|
||||
and not settings.ADVANCED_MODE
|
||||
)
|
||||
access_token = security.create_access_token(
|
||||
access_token = create_access_token(
|
||||
userid=user_or_message.id,
|
||||
username=user_or_message.name,
|
||||
super_user=user_or_message.is_superuser,
|
||||
expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||
level=level,
|
||||
)
|
||||
security.set_or_refresh_resource_token_cookie(
|
||||
set_or_refresh_resource_token_cookie(
|
||||
request,
|
||||
response,
|
||||
_SchemaTokenPayload(
|
||||
|
||||
@@ -13,7 +13,7 @@ from app.schemas.mcp import ToolCallRequest as _SchemaToolCallRequest
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
|
||||
from app.agent.tools.manager import moviepilot_tool_manager
|
||||
from app.application.security.access import verify_apikey
|
||||
from app.adapters.web.security.access import verify_apikey
|
||||
from app.runtime.log import logger
|
||||
|
||||
# 导入版本号
|
||||
|
||||
@@ -25,8 +25,7 @@ from app.domain.context import Context, MusicInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.application.security.access import verify_token, verify_apitoken
|
||||
from app.db.models import User
|
||||
from app.adapters.web.security.access import verify_token, verify_apitoken
|
||||
from app.api.deps import get_current_active_user, get_current_active_superuser
|
||||
from app.schemas.category import CategoryConfig
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource, MediaType
|
||||
@@ -440,7 +439,7 @@ def scrape(
|
||||
summary="获取分类策略配置",
|
||||
response_model=_SchemaResponse[_SchemaCategoryConfig],
|
||||
)
|
||||
def get_category_config(_: User = Depends(get_current_active_user)):
|
||||
def get_category_config(_: object = Depends(get_current_active_user)):
|
||||
"""
|
||||
获取分类策略配置
|
||||
"""
|
||||
@@ -452,7 +451,7 @@ def get_category_config(_: User = Depends(get_current_active_user)):
|
||||
"/category/config", summary="保存分类策略配置", response_model=_SchemaResponse[None]
|
||||
)
|
||||
def save_category_config(
|
||||
config: CategoryConfig, _: User = Depends(get_current_active_superuser)
|
||||
config: CategoryConfig, _: object = Depends(get_current_active_superuser)
|
||||
):
|
||||
"""
|
||||
保存分类策略配置
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.schemas.common import ServiceClientInfo as _SchemaServiceClientInfo
|
||||
from app.schemas.mediaserver import ExistMediaInfo as _SchemaExistMediaInfo
|
||||
@@ -19,12 +18,10 @@ from app.chain.download import DownloadChain
|
||||
from app.chain.mediaserver import MediaServerChain
|
||||
from app.domain.context import MediaInfo
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.application.security.access import verify_token
|
||||
from app.db import get_async_db
|
||||
from app.db.oper.mediaserver import MediaServerOper
|
||||
from app.db.models import MediaServerItem
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.mediaserver import MediaServerHelper
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.mediaserver import MediaServerHelper, MediaServerQueryService
|
||||
from app.api.deps import get_mediaserver_query_service
|
||||
from app.schemas.mediaserver import NotExistMediaInfo
|
||||
from app.schemas.types import MediaSource, MediaType, SystemConfigKey
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
@@ -90,7 +87,7 @@ async def exists_local(
|
||||
media_source: Optional[MediaSource] = None,
|
||||
media_id: Optional[str] = None,
|
||||
season: Optional[int] = None,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: MediaServerQueryService = Depends(get_mediaserver_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -107,7 +104,7 @@ async def exists_local(
|
||||
# 返回对象
|
||||
ret_info = {}
|
||||
# 本地数据库是否存在
|
||||
exist: MediaServerItem = await MediaServerOper(db).async_exists(
|
||||
item_id = await service.find_item_id(
|
||||
title=meta.name if meta else None,
|
||||
year=year,
|
||||
mtype=mtype,
|
||||
@@ -115,8 +112,8 @@ async def exists_local(
|
||||
media_id=media_id,
|
||||
season=season,
|
||||
)
|
||||
if exist:
|
||||
ret_info = {"id": exist.item_id}
|
||||
if item_id:
|
||||
ret_info = {"id": item_id}
|
||||
return _SchemaResponse(success=True, data={"item": ret_info})
|
||||
|
||||
|
||||
@@ -251,7 +248,7 @@ async def clients(_: _SchemaTokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询可用媒体服务器
|
||||
"""
|
||||
mediaservers: List[dict] = SystemConfigOper().get(SystemConfigKey.MediaServers)
|
||||
mediaservers: List[dict] = get_configured_system_config().get(SystemConfigKey.MediaServers)
|
||||
if mediaservers:
|
||||
return [
|
||||
{"name": d.get("name"), "type": d.get("type")}
|
||||
|
||||
@@ -5,7 +5,6 @@ import time
|
||||
from typing import Protocol, Union, Any, List, Optional
|
||||
|
||||
from fastapi import BackgroundTasks, Depends, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.responses import PlainTextResponse
|
||||
|
||||
from app.schemas.message import MessageClearBefore as _SchemaMessageClearBefore
|
||||
@@ -20,13 +19,12 @@ from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.message import MessageChain
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.application.security.access import verify_token, verify_apitoken
|
||||
from app.db import get_async_db
|
||||
from app.db.models import User
|
||||
from app.db.oper.message import MessageOper
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.api.deps import get_current_active_superuser
|
||||
from app.runtime.extensions.service_registry import ServiceConfigHelper
|
||||
from app.adapters.web.security.access import verify_token, verify_apitoken
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import get_current_active_superuser, get_message_query_service
|
||||
from app.application.messaging.message import MessageQueryService
|
||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
||||
from app.runtime.log import logger
|
||||
from app.adapters.external.wechat_crypt import WXBizMsgCrypt
|
||||
from app.schemas.types import NotificationChannel, SystemConfigKey
|
||||
@@ -83,7 +81,7 @@ def _get_notification_clear_before() -> _SchemaMessageClearBefore:
|
||||
"""
|
||||
读取通知中心清理时间配置。
|
||||
"""
|
||||
value = SystemConfigOper().get(SystemConfigKey.NotificationClearBefore)
|
||||
value = get_configured_system_config().get(SystemConfigKey.NotificationClearBefore)
|
||||
if isinstance(value, dict):
|
||||
return _SchemaMessageClearBefore(
|
||||
all=_normalize_notification_clear_timestamp(value.get("all")),
|
||||
@@ -156,7 +154,7 @@ async def user_message(
|
||||
async def web_message(
|
||||
request: Request,
|
||||
text: Optional[str] = None,
|
||||
current_user: User = Depends(get_current_active_superuser),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
):
|
||||
"""
|
||||
WEB消息响应
|
||||
@@ -194,28 +192,20 @@ async def web_message(
|
||||
@router.get("/web", summary="获取WEB消息", response_model=List[_SchemaWebMessageItem])
|
||||
async def get_web_message(
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: MessageQueryService = Depends(get_message_query_service),
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 20,
|
||||
):
|
||||
"""
|
||||
获取WEB消息列表
|
||||
"""
|
||||
ret_messages = []
|
||||
messages = await MessageOper(db).async_list_by_page(page=page, count=count)
|
||||
for message in messages:
|
||||
try:
|
||||
ret_messages.append(message.to_dict())
|
||||
except Exception as e:
|
||||
logger.error(f"获取WEB消息列表失败: {str(e)}")
|
||||
continue
|
||||
return ret_messages
|
||||
return await service.list_web(page=page, count=count)
|
||||
|
||||
|
||||
@router.get("/notification", summary="获取通知消息", response_model=List[_SchemaMessageHistoryItem])
|
||||
async def get_notification_message(
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: MessageQueryService = Depends(get_message_query_service),
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 20,
|
||||
):
|
||||
@@ -223,14 +213,14 @@ async def get_notification_message(
|
||||
获取系统发送的通知消息列表。
|
||||
"""
|
||||
clear_before = _get_notification_clear_before()
|
||||
messages = await MessageOper(db).async_list_sent_by_page(
|
||||
messages = await service.list_notifications(
|
||||
page=page,
|
||||
count=count,
|
||||
all_clear_before=_format_notification_clear_time(clear_before.all),
|
||||
system_clear_before=_format_notification_clear_time(clear_before.system),
|
||||
media_clear_before=_format_notification_clear_time(clear_before.media),
|
||||
)
|
||||
return [_SchemaMessageHistoryItem(**message.to_dict()) for message in messages]
|
||||
return [_SchemaMessageHistoryItem(**message) for message in messages]
|
||||
|
||||
|
||||
@router.delete(
|
||||
@@ -248,7 +238,7 @@ async def clear_notification_message(
|
||||
clear_before = _get_notification_clear_before()
|
||||
value = clear_before.model_dump()
|
||||
value[scope.value] = int(time.time() * 1000)
|
||||
await SystemConfigOper().async_set(SystemConfigKey.NotificationClearBefore, value)
|
||||
await get_configured_system_config().async_set(SystemConfigKey.NotificationClearBefore, value)
|
||||
return _SchemaResponse(success=True, data={"clear_before": value})
|
||||
|
||||
|
||||
|
||||
+75
-77
@@ -4,12 +4,9 @@ MFA (Multi-Factor Authentication) API 端点
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from typing import Any, Annotated, Optional
|
||||
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from fastapi import Depends, HTTPException, Body, Request, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.schemas.mcp import BaseModel as _SchemaBaseModel
|
||||
from app.schemas.mcp import JsonData as _SchemaJsonData
|
||||
@@ -21,13 +18,24 @@ from app.schemas.response import Response as _SchemaResponse
|
||||
from app.schemas.token import Token as _SchemaToken
|
||||
from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
|
||||
from app.application.security import access as security
|
||||
from app.runtime.config import settings
|
||||
from app.db import get_async_db
|
||||
from app.db.models.passkey import PassKey
|
||||
from app.db.models.user import User
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.api.deps import get_current_active_user, get_current_active_user_async
|
||||
from app.adapters.web.security.access import set_or_refresh_resource_token_cookie
|
||||
from app.application.security.token import verify_password
|
||||
from app.application.security.auth import get_configured_auth_service
|
||||
from app.application.security.user import UserService
|
||||
from app.application.security.user import (
|
||||
get_configured_user_id_lookup,
|
||||
get_configured_user_name_lookup,
|
||||
)
|
||||
from app.application.security.passkeys import (
|
||||
PasskeyService,
|
||||
)
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.api.deps import (
|
||||
get_current_active_user,
|
||||
get_current_active_user_async,
|
||||
get_user_service,
|
||||
get_passkey_service,
|
||||
)
|
||||
from app.application.security.passkey import (
|
||||
PassKeyHelper,
|
||||
PassKeyRegistrationOriginMismatchError,
|
||||
@@ -35,7 +43,6 @@ from app.application.security.passkey import (
|
||||
PasskeyChallengeStore,
|
||||
)
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.application.security.otp import OtpUtils
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
@@ -43,7 +50,7 @@ router = ResponseAPIRouter()
|
||||
# ==================== 辅助函数 ====================
|
||||
|
||||
|
||||
def _build_credential_list(passkeys: list[PassKey]) -> list[dict[str, Any]]:
|
||||
def _build_credential_list(passkeys: list[Any]) -> list[dict[str, Any]]:
|
||||
"""
|
||||
构建凭证列表
|
||||
|
||||
@@ -75,7 +82,10 @@ def _extract_and_standardize_credential_id(credential: dict) -> str:
|
||||
|
||||
|
||||
def _verify_passkey_and_update(
|
||||
credential: dict, challenge: str, passkey: PassKey
|
||||
credential: dict,
|
||||
challenge: str,
|
||||
passkey: Any,
|
||||
service: PasskeyService,
|
||||
) -> tuple[bool, int]:
|
||||
"""
|
||||
验证 PassKey 并更新使用时间和签名计数
|
||||
@@ -93,7 +103,7 @@ def _verify_passkey_and_update(
|
||||
)
|
||||
|
||||
if success:
|
||||
passkey.update_last_used(db=None, sign_count=new_sign_count)
|
||||
service.update_last_used(passkey, new_sign_count)
|
||||
|
||||
return success, new_sign_count
|
||||
|
||||
@@ -129,11 +139,14 @@ class PassKeyDeleteRequest(_SchemaBaseModel):
|
||||
summary="判断用户是否开启二次验证",
|
||||
response_model=_SchemaResponse[_SchemaMfaStatusData],
|
||||
)
|
||||
async def mfa_status(username: str, db: AsyncSession = Depends(get_async_db)) -> Any:
|
||||
async def mfa_status(
|
||||
username: str,
|
||||
service: UserService = Depends(get_user_service),
|
||||
) -> Any:
|
||||
"""
|
||||
检查指定用户是否启用了二次验证
|
||||
"""
|
||||
user: User = await User.async_get_by_name(db, username)
|
||||
user = await service.get_by_name(username)
|
||||
if not user:
|
||||
return _SchemaResponse(success=False, message="用户不存在")
|
||||
|
||||
@@ -152,7 +165,7 @@ async def mfa_status(username: str, db: AsyncSession = Depends(get_async_db)) ->
|
||||
response_model=_SchemaResponse[_SchemaOtpGenerateData],
|
||||
)
|
||||
def otp_generate(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
|
||||
) -> Any:
|
||||
"""生成 OTP 密钥及对应的 URI"""
|
||||
secret, uri = OtpUtils.generate_secret_key(current_user.name)
|
||||
@@ -162,14 +175,16 @@ def otp_generate(
|
||||
@router.post("/otp/verify", summary="绑定并验证 OTP", response_model=_SchemaResponse[None])
|
||||
async def otp_verify(
|
||||
data: OtpVerifyRequest,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
service: UserService = Depends(get_user_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""验证用户输入的 OTP 码,验证通过后正式开启 OTP 验证"""
|
||||
if not OtpUtils.is_legal(data.uri, data.otpPassword):
|
||||
return _SchemaResponse(success=False, message="验证码错误")
|
||||
await current_user.async_update_otp_by_name(
|
||||
db, current_user.name, True, OtpUtils.get_secret(data.uri)
|
||||
await service.update_otp(
|
||||
current_user.name,
|
||||
True,
|
||||
OtpUtils.get_secret(data.uri),
|
||||
)
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
@@ -181,14 +196,14 @@ async def otp_verify(
|
||||
)
|
||||
async def otp_disable(
|
||||
data: OtpDisableRequest,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
service: UserService = Depends(get_user_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""关闭当前用户的 OTP 验证功能"""
|
||||
# 验证密码
|
||||
if not security.verify_password(data.password, str(current_user.hashed_password)):
|
||||
if not verify_password(data.password, str(current_user.hashed_password)):
|
||||
return _SchemaResponse(success=False, message="密码错误")
|
||||
await current_user.async_update_otp_by_name(db, current_user.name, False, "")
|
||||
await service.update_otp(current_user.name, False, "")
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@@ -228,12 +243,13 @@ class PassKeyAuthenticationFinish(_SchemaBaseModel):
|
||||
response_model=_SchemaResponse[_SchemaPasskeyStartData],
|
||||
)
|
||||
def passkey_register_start(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
|
||||
service: PasskeyService = Depends(get_passkey_service),
|
||||
) -> Any:
|
||||
"""开始注册 PassKey - 生成注册选项"""
|
||||
try:
|
||||
# 获取用户已有的PassKey
|
||||
existing_passkeys = PassKey.get_by_user_id(db=None, user_id=current_user.id)
|
||||
existing_passkeys = service.list_by_user_id(current_user.id)
|
||||
existing_credentials = (
|
||||
_build_credential_list(existing_passkeys) if existing_passkeys else None
|
||||
)
|
||||
@@ -272,7 +288,8 @@ def passkey_register_start(
|
||||
)
|
||||
def passkey_register_finish(
|
||||
passkey_req: PassKeyRegistrationFinish,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
|
||||
service: PasskeyService = Depends(get_passkey_service),
|
||||
) -> Any:
|
||||
"""完成注册 PassKey - 验证并保存凭证"""
|
||||
try:
|
||||
@@ -303,16 +320,15 @@ def passkey_register_finish(
|
||||
transports = ",".join(passkey_req.credential["response"]["transports"])
|
||||
|
||||
# 保存到数据库
|
||||
passkey = PassKey(
|
||||
user_id=current_user.id,
|
||||
credential_id=credential_id,
|
||||
public_key=public_key,
|
||||
sign_count=sign_count,
|
||||
name=passkey_req.name or "通行密钥",
|
||||
aaguid=aaguid,
|
||||
transports=transports,
|
||||
)
|
||||
passkey.create()
|
||||
service.create({
|
||||
"user_id": current_user.id,
|
||||
"credential_id": credential_id,
|
||||
"public_key": public_key,
|
||||
"sign_count": sign_count,
|
||||
"name": passkey_req.name or "通行密钥",
|
||||
"aaguid": aaguid,
|
||||
"transports": transports,
|
||||
})
|
||||
|
||||
logger.info(f"用户 {current_user.name} 成功注册PassKey: {passkey_req.name}")
|
||||
|
||||
@@ -339,6 +355,7 @@ def passkey_register_finish(
|
||||
)
|
||||
def passkey_authenticate_start(
|
||||
passkey_req: PassKeyAuthenticationStart = Body(...),
|
||||
service: PasskeyService = Depends(get_passkey_service),
|
||||
) -> Any:
|
||||
"""开始 PassKey 认证 - 生成认证选项"""
|
||||
try:
|
||||
@@ -347,9 +364,9 @@ def passkey_authenticate_start(
|
||||
|
||||
# 如果指定了用户名,只允许该用户的PassKey
|
||||
if passkey_req.username:
|
||||
user = User.get_by_name(db=None, name=passkey_req.username)
|
||||
user = get_configured_user_name_lookup()(passkey_req.username)
|
||||
existing_passkeys = (
|
||||
PassKey.get_by_user_id(db=None, user_id=user.id) if user else None
|
||||
service.list_by_user_id(user.id) if user else None
|
||||
)
|
||||
|
||||
if not user or not existing_passkeys:
|
||||
@@ -387,7 +404,10 @@ def passkey_authenticate_start(
|
||||
openapi_extra={RAW_RESPONSE_OPENAPI_KEY: True},
|
||||
)
|
||||
def passkey_authenticate_finish(
|
||||
request: Request, response: Response, passkey_req: PassKeyAuthenticationFinish
|
||||
request: Request,
|
||||
response: Response,
|
||||
passkey_req: PassKeyAuthenticationFinish,
|
||||
service: PasskeyService = Depends(get_passkey_service),
|
||||
) -> Any:
|
||||
"""完成 PassKey 认证 - 验证凭证并返回 token"""
|
||||
try:
|
||||
@@ -408,8 +428,8 @@ def passkey_authenticate_finish(
|
||||
raise HTTPException(status_code=401, detail="认证失败")
|
||||
|
||||
# 查找PassKey并获取用户
|
||||
passkey = PassKey.get_by_credential_id(db=None, credential_id=credential_id)
|
||||
user = User.get_by_id(db=None, user_id=passkey.user_id) if passkey else None
|
||||
passkey = service.get_by_credential_id(credential_id)
|
||||
user = get_configured_user_id_lookup()(passkey.user_id) if passkey else None
|
||||
if not passkey or not user or not user.is_active:
|
||||
raise HTTPException(status_code=401, detail="认证失败")
|
||||
if challenge_state.user_id is not None and challenge_state.user_id != user.id:
|
||||
@@ -420,6 +440,7 @@ def passkey_authenticate_finish(
|
||||
credential=passkey_req.credential,
|
||||
challenge=challenge_state.challenge,
|
||||
passkey=passkey,
|
||||
service=service,
|
||||
)
|
||||
|
||||
if not success:
|
||||
@@ -428,42 +449,19 @@ def passkey_authenticate_finish(
|
||||
logger.info(f"用户 {user.name} 通过PassKey认证成功")
|
||||
|
||||
# 生成token
|
||||
level = SitesHelper().auth_level
|
||||
show_wizard = (
|
||||
not SystemConfigOper().get(SystemConfigKey.SetupWizardState)
|
||||
and not settings.ADVANCED_MODE
|
||||
)
|
||||
|
||||
access_token = security.create_access_token(
|
||||
userid=user.id,
|
||||
username=user.name,
|
||||
super_user=user.is_superuser,
|
||||
expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
|
||||
level=level,
|
||||
)
|
||||
security.set_or_refresh_resource_token_cookie(
|
||||
token = get_configured_auth_service().build_token_response(user)
|
||||
set_or_refresh_resource_token_cookie(
|
||||
request,
|
||||
response,
|
||||
_SchemaTokenPayload(
|
||||
sub=user.id,
|
||||
username=user.name,
|
||||
super_user=user.is_superuser,
|
||||
level=level,
|
||||
level=token.level,
|
||||
purpose="authentication",
|
||||
),
|
||||
)
|
||||
|
||||
return _SchemaToken(
|
||||
access_token=access_token,
|
||||
token_type="bearer",
|
||||
super_user=user.is_superuser,
|
||||
user_id=user.id,
|
||||
user_name=user.name,
|
||||
avatar=user.avatar,
|
||||
level=level,
|
||||
permissions=user.permissions or {},
|
||||
wizard=show_wizard,
|
||||
)
|
||||
return token
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -477,11 +475,12 @@ def passkey_authenticate_finish(
|
||||
response_model=_SchemaResponse[list[_SchemaPasskeyInfo]],
|
||||
)
|
||||
def passkey_list(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
current_user: Annotated[ApiPrincipal, Depends(get_current_active_user)],
|
||||
service: PasskeyService = Depends(get_passkey_service),
|
||||
) -> Any:
|
||||
"""获取当前用户的所有 PassKey"""
|
||||
try:
|
||||
passkeys = PassKey.get_by_user_id(db=None, user_id=current_user.id)
|
||||
passkeys = service.list_by_user_id(current_user.id)
|
||||
|
||||
key_list = (
|
||||
[
|
||||
@@ -514,19 +513,18 @@ def passkey_list(
|
||||
)
|
||||
async def passkey_delete(
|
||||
data: PassKeyDeleteRequest,
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
service: PasskeyService = Depends(get_passkey_service),
|
||||
) -> Any:
|
||||
"""删除指定的 PassKey"""
|
||||
try:
|
||||
# 验证密码
|
||||
if not security.verify_password(
|
||||
if not verify_password(
|
||||
data.password, str(current_user.hashed_password)
|
||||
):
|
||||
return _SchemaResponse(success=False, message="密码错误")
|
||||
|
||||
success = PassKey.delete_by_id(
|
||||
db=None, passkey_id=data.passkey_id, user_id=current_user.id
|
||||
)
|
||||
success = service.delete_by_id(data.passkey_id, current_user.id)
|
||||
|
||||
if success:
|
||||
logger.info(f"用户 {current_user.name} 删除了PassKey: {data.passkey_id}")
|
||||
|
||||
@@ -14,8 +14,7 @@ from app.chain.media import MediaChain
|
||||
from app.chain.recommend import RecommendChain
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo
|
||||
from app.application.security.access import verify_token
|
||||
from app.db.models.user import User
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.deps import get_current_active_superuser_async
|
||||
from app.chain.listenbrainz import (
|
||||
LISTENBRAINZ_CHART_RANGES,
|
||||
@@ -114,7 +113,7 @@ async def recognize_music(
|
||||
response_model=_SchemaResponse[_SchemaMusicRecognitionCacheData],
|
||||
)
|
||||
async def music_recognition_cache(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""查询可管理的 MusicBrainz 识别缓存。"""
|
||||
cache_items = MusicBrainzChain().cache_items()
|
||||
@@ -137,7 +136,7 @@ async def music_recognition_cache(
|
||||
)
|
||||
async def delete_music_recognition_cache(
|
||||
cache_key: str,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""按缓存键删除单条 MusicBrainz 识别缓存。"""
|
||||
deleted_item = MusicBrainzChain().delete_cache(cache_key)
|
||||
@@ -150,7 +149,7 @@ async def delete_music_recognition_cache(
|
||||
"/cache", summary="清空音乐识别缓存", response_model=_SchemaResponse[None]
|
||||
)
|
||||
async def clear_music_recognition_cache(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""清空全部 MusicBrainz 识别缓存。"""
|
||||
MusicBrainzChain().clear_cache()
|
||||
|
||||
@@ -6,7 +6,6 @@ from app.schemas.common import ManageRequest as _SchemaManageRequest
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.notification import NotificationChain
|
||||
from app.db.models import User
|
||||
from app.api.deps import get_current_active_superuser
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
@@ -19,7 +18,7 @@ router = ResponseAPIRouter()
|
||||
)
|
||||
def manage_channel(
|
||||
request: _SchemaManageRequest,
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: object = Depends(get_current_active_superuser),
|
||||
):
|
||||
"""
|
||||
通知渠道统一管理入口
|
||||
|
||||
@@ -32,7 +32,7 @@ from app.agent.runtime_loader import (
|
||||
)
|
||||
from app.agent.contracts import ReplyMode
|
||||
from app.runtime.config import settings
|
||||
from app.application.security.access import openai_bearer_scheme
|
||||
from app.adapters.web.security.access import openai_bearer_scheme
|
||||
from app.schemas.types import NotificationChannel
|
||||
|
||||
OPENAI_ERROR_RESPONSES = {
|
||||
|
||||
+52
-43
@@ -35,14 +35,18 @@ from app.application.commands import init_commands
|
||||
from app.application.scheduling import remove_plugin_job, update_plugin_job
|
||||
from app.runtime.cache import async_fresh
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.extensions.plugin_manager import PluginManager
|
||||
from app.application.security.access import (
|
||||
from app.application.plugin.runtime import get_plugin_manager as PluginManager
|
||||
from app.runtime.extensions.plugin.contracts import (
|
||||
PluginDashboardError,
|
||||
PluginNotFoundError,
|
||||
)
|
||||
from app.adapters.web.security.access import (
|
||||
resource_token_cookie,
|
||||
verify_resource_token,
|
||||
verify_token,
|
||||
)
|
||||
from app.db.models import User
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import (
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
@@ -240,7 +244,7 @@ async def _get_plugin_history_detail(
|
||||
|
||||
@router.get("/", summary="所有插件", response_model=List[_SchemaPlugin])
|
||||
async def all_plugins(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
state: Optional[str] = "all",
|
||||
force: bool = False,
|
||||
) -> List[_SchemaPlugin]:
|
||||
@@ -298,17 +302,17 @@ async def all_plugins(
|
||||
|
||||
|
||||
@router.get("/installed", summary="已安装插件", response_model=List[str])
|
||||
async def installed(_: User = Depends(get_current_active_superuser_async)) -> Any:
|
||||
async def installed(_: ApiPrincipal = Depends(get_current_active_superuser_async)) -> Any:
|
||||
"""
|
||||
查询用户已安装插件清单
|
||||
"""
|
||||
return SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
return get_configured_system_config().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
|
||||
|
||||
@router.get("/history/{plugin_id}", summary="获取插件更新说明", response_model=_SchemaPlugin)
|
||||
async def plugin_history(
|
||||
plugin_id: str,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
force: bool = True,
|
||||
) -> _SchemaPlugin:
|
||||
"""
|
||||
@@ -330,7 +334,7 @@ async def plugin_history(
|
||||
)
|
||||
async def plugin_releases(
|
||||
plugin_id: str,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
repo_url: Optional[str] = "",
|
||||
force: bool = False,
|
||||
) -> dict:
|
||||
@@ -405,7 +409,7 @@ async def statistic(_: _SchemaTokenPayload = Depends(verify_token)) -> Any:
|
||||
)
|
||||
async def plugin_ratings(
|
||||
plugin_ids: Optional[str] = None,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> Dict[str, _SchemaPluginRating]:
|
||||
"""
|
||||
批量查询插件平均分、评分人数和当前安装实例评分。
|
||||
@@ -425,7 +429,7 @@ async def plugin_ratings(
|
||||
)
|
||||
async def plugin_rating(
|
||||
plugin_id: str,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaPluginRating:
|
||||
"""
|
||||
查询单个插件平均分、评分人数和当前安装实例评分。
|
||||
@@ -442,12 +446,12 @@ async def plugin_rating(
|
||||
async def rate_plugin(
|
||||
plugin_id: str,
|
||||
payload: _SchemaPluginRatingRequest,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
为已安装插件新增或更新当前安装实例评分。
|
||||
"""
|
||||
installed_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
installed_plugins = get_configured_system_config().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
if plugin_id not in installed_plugins:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -467,7 +471,7 @@ async def rate_plugin(
|
||||
"/reload/{plugin_id}", summary="重新加载插件", response_model=_SchemaResponse[None]
|
||||
)
|
||||
def reload_plugin(
|
||||
plugin_id: str, _: User = Depends(get_current_active_superuser)
|
||||
plugin_id: str, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> Any:
|
||||
"""
|
||||
重新加载插件
|
||||
@@ -485,7 +489,7 @@ async def install(
|
||||
repo_url: Optional[str] = "",
|
||||
release_version: Optional[str] = None,
|
||||
force: Optional[bool] = False,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
安装插件
|
||||
@@ -495,7 +499,7 @@ async def install(
|
||||
|
||||
async def save_installed_plugins(plugin_ids: List[str]) -> object:
|
||||
"""保存安装用例确认后的插件列表。"""
|
||||
return await SystemConfigOper().async_set(
|
||||
return await get_configured_system_config().async_set(
|
||||
SystemConfigKey.UserInstalledPlugins,
|
||||
plugin_ids,
|
||||
)
|
||||
@@ -523,7 +527,7 @@ async def install(
|
||||
return await run_in_threadpool(register_plugin, target_id)
|
||||
|
||||
command = PluginInstallCommand(
|
||||
installed_plugins_reader=lambda: SystemConfigOper().get(
|
||||
installed_plugins_reader=lambda: get_configured_system_config().get(
|
||||
SystemConfigKey.UserInstalledPlugins
|
||||
) or [],
|
||||
installed_plugins_writer=save_installed_plugins,
|
||||
@@ -585,7 +589,7 @@ def plugin_sidebar_nav(_: _SchemaTokenPayload = Depends(verify_token)) -> Any:
|
||||
response_model=_SchemaJsonObject,
|
||||
)
|
||||
def plugin_form(
|
||||
plugin_id: str, _: User = Depends(get_current_active_superuser)
|
||||
plugin_id: str, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> dict:
|
||||
"""
|
||||
根据插件ID获取插件配置表单或Vue组件URL
|
||||
@@ -621,7 +625,7 @@ def plugin_form(
|
||||
response_model=_SchemaJsonObject,
|
||||
)
|
||||
def plugin_page(
|
||||
plugin_id: str, _: User = Depends(get_current_active_superuser)
|
||||
plugin_id: str, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> dict:
|
||||
"""
|
||||
根据插件ID获取插件数据页面
|
||||
@@ -649,7 +653,7 @@ def plugin_page(
|
||||
response_model=List[_SchemaPluginDashboardMetaItem],
|
||||
)
|
||||
def plugin_dashboard_meta(
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
) -> List[dict]:
|
||||
"""
|
||||
获取所有插件仪表板元信息
|
||||
@@ -662,19 +666,24 @@ def plugin_dashboard_by_key(
|
||||
plugin_id: str,
|
||||
key: str,
|
||||
user_agent: Annotated[str | None, Header()] = None,
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
) -> Optional[_SchemaPluginDashboard]:
|
||||
"""
|
||||
根据插件ID获取插件仪表板
|
||||
"""
|
||||
return PluginManager().get_plugin_dashboard(plugin_id, key, user_agent)
|
||||
try:
|
||||
return PluginManager().get_plugin_dashboard(plugin_id, key, user_agent)
|
||||
except PluginNotFoundError as error:
|
||||
raise HTTPException(status_code=404, detail=str(error)) from error
|
||||
except PluginDashboardError as error:
|
||||
raise HTTPException(status_code=500, detail=str(error)) from error
|
||||
|
||||
|
||||
@router.get("/dashboard/{plugin_id}", summary="获取插件仪表板配置")
|
||||
def plugin_dashboard(
|
||||
plugin_id: str,
|
||||
user_agent: Annotated[str | None, Header()] = None,
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
) -> Optional[_SchemaPluginDashboard]:
|
||||
"""
|
||||
根据插件ID获取插件仪表板
|
||||
@@ -687,7 +696,7 @@ def plugin_dashboard(
|
||||
)
|
||||
def reset_plugin(
|
||||
plugin_id: str,
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
command: PluginConfigCommand = Depends(get_plugin_config_command),
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -798,13 +807,13 @@ async def plugin_static_file(
|
||||
response_model=_SchemaPluginFoldersData,
|
||||
)
|
||||
async def get_plugin_folders(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> dict:
|
||||
"""
|
||||
获取插件文件夹分组配置
|
||||
"""
|
||||
try:
|
||||
result = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {}
|
||||
result = get_configured_system_config().get(SystemConfigKey.PluginFolders) or {}
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"[文件夹API] 获取文件夹配置失败: {str(e)}")
|
||||
@@ -813,13 +822,13 @@ async def get_plugin_folders(
|
||||
|
||||
@router.post("/folders", summary="保存插件文件夹配置", response_model=_SchemaResponse[None])
|
||||
async def save_plugin_folders(
|
||||
folders: dict, _: User = Depends(get_current_active_superuser_async)
|
||||
folders: dict, _: ApiPrincipal = Depends(get_current_active_superuser_async)
|
||||
) -> Any:
|
||||
"""
|
||||
保存插件文件夹分组配置
|
||||
"""
|
||||
try:
|
||||
SystemConfigOper().set(SystemConfigKey.PluginFolders, folders)
|
||||
get_configured_system_config().set(SystemConfigKey.PluginFolders, folders)
|
||||
return _SchemaResponse(success=True)
|
||||
except Exception as e:
|
||||
logger.error(f"[文件夹API] 保存文件夹配置失败: {str(e)}")
|
||||
@@ -830,15 +839,15 @@ async def save_plugin_folders(
|
||||
"/folders/{folder_name}", summary="创建插件文件夹", response_model=_SchemaResponse[None]
|
||||
)
|
||||
async def create_plugin_folder(
|
||||
folder_name: str, _: User = Depends(get_current_active_superuser_async)
|
||||
folder_name: str, _: ApiPrincipal = Depends(get_current_active_superuser_async)
|
||||
) -> Any:
|
||||
"""
|
||||
创建新的插件文件夹
|
||||
"""
|
||||
folders = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {}
|
||||
folders = get_configured_system_config().get(SystemConfigKey.PluginFolders) or {}
|
||||
if folder_name not in folders:
|
||||
folders[folder_name] = []
|
||||
SystemConfigOper().set(SystemConfigKey.PluginFolders, folders)
|
||||
get_configured_system_config().set(SystemConfigKey.PluginFolders, folders)
|
||||
return _SchemaResponse(
|
||||
success=True, message=f"文件夹 '{folder_name}' 创建成功"
|
||||
)
|
||||
@@ -850,15 +859,15 @@ async def create_plugin_folder(
|
||||
"/folders/{folder_name}", summary="删除插件文件夹", response_model=_SchemaResponse[None]
|
||||
)
|
||||
async def delete_plugin_folder(
|
||||
folder_name: str, _: User = Depends(get_current_active_superuser_async)
|
||||
folder_name: str, _: ApiPrincipal = Depends(get_current_active_superuser_async)
|
||||
) -> Any:
|
||||
"""
|
||||
删除插件文件夹
|
||||
"""
|
||||
folders = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {}
|
||||
folders = get_configured_system_config().get(SystemConfigKey.PluginFolders) or {}
|
||||
if folder_name in folders:
|
||||
del folders[folder_name]
|
||||
await SystemConfigOper().async_set(SystemConfigKey.PluginFolders, folders)
|
||||
await get_configured_system_config().async_set(SystemConfigKey.PluginFolders, folders)
|
||||
return _SchemaResponse(
|
||||
success=True, message=f"文件夹 '{folder_name}' 删除成功"
|
||||
)
|
||||
@@ -874,14 +883,14 @@ async def delete_plugin_folder(
|
||||
async def update_folder_plugins(
|
||||
folder_name: str,
|
||||
plugin_ids: List[str],
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
更新指定文件夹中的插件列表
|
||||
"""
|
||||
folders = SystemConfigOper().get(SystemConfigKey.PluginFolders) or {}
|
||||
folders = get_configured_system_config().get(SystemConfigKey.PluginFolders) or {}
|
||||
folders[folder_name] = plugin_ids
|
||||
await SystemConfigOper().async_set(SystemConfigKey.PluginFolders, folders)
|
||||
await get_configured_system_config().async_set(SystemConfigKey.PluginFolders, folders)
|
||||
return _SchemaResponse(
|
||||
success=True, message=f"文件夹 '{folder_name}' 中的插件已更新"
|
||||
)
|
||||
@@ -891,7 +900,7 @@ async def update_folder_plugins(
|
||||
"/clone/{plugin_id}", summary="创建插件分身", response_model=_SchemaResponse[None]
|
||||
)
|
||||
def clone_plugin(
|
||||
plugin_id: str, clone_data: dict, _: User = Depends(get_current_active_superuser)
|
||||
plugin_id: str, clone_data: dict, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> Any:
|
||||
"""
|
||||
创建插件分身
|
||||
@@ -925,7 +934,7 @@ def clone_plugin(
|
||||
response_model=_SchemaJsonObject,
|
||||
)
|
||||
async def plugin_config(
|
||||
plugin_id: str, _: User = Depends(get_current_active_superuser_async)
|
||||
plugin_id: str, _: ApiPrincipal = Depends(get_current_active_superuser_async)
|
||||
) -> dict:
|
||||
"""
|
||||
根据插件ID获取插件配置信息
|
||||
@@ -937,7 +946,7 @@ async def plugin_config(
|
||||
def set_plugin_config(
|
||||
plugin_id: str,
|
||||
conf: dict,
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
command: PluginConfigCommand = Depends(get_plugin_config_command),
|
||||
) -> Any:
|
||||
"""
|
||||
@@ -949,12 +958,12 @@ def set_plugin_config(
|
||||
|
||||
@router.delete("/{plugin_id}", summary="卸载插件", response_model=_SchemaResponse[None])
|
||||
def uninstall_plugin(
|
||||
plugin_id: str, _: User = Depends(get_current_active_superuser)
|
||||
plugin_id: str, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> Any:
|
||||
"""
|
||||
卸载插件
|
||||
"""
|
||||
config_oper = SystemConfigOper()
|
||||
config_oper = get_configured_system_config()
|
||||
# 删除已安装信息
|
||||
install_plugins = config_oper.get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
for plugin in install_plugins:
|
||||
@@ -995,7 +1004,7 @@ def _add_clone_to_plugin_folder(original_plugin_id: str, clone_plugin_id: str):
|
||||
:param clone_plugin_id: 分身插件ID
|
||||
"""
|
||||
try:
|
||||
config_oper = SystemConfigOper()
|
||||
config_oper = get_configured_system_config()
|
||||
# 获取插件文件夹配置
|
||||
folders = config_oper.get(SystemConfigKey.PluginFolders) or {}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.recommend import RecommendChain
|
||||
from app.runtime.events import eventmanager
|
||||
from app.application.security.access import verify_token
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.schemas.exception import TMDbException
|
||||
from app.schemas.event import RecommendSourceEventData
|
||||
from app.schemas.types import ChainEventType
|
||||
|
||||
@@ -16,7 +16,7 @@ from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
from app.schemas.workflow import Context as _SchemaContext
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.search import SearchChain
|
||||
from app.application.security.access import verify_resource_token, verify_token
|
||||
from app.adapters.web.security.access import verify_resource_token, verify_token
|
||||
from app.runtime.localization import LocaleHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
+80
-93
@@ -1,8 +1,6 @@
|
||||
from typing import List, Any, Dict, Optional
|
||||
|
||||
from fastapi import Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
from app.schemas.common import JsonObject as _SchemaJsonObject
|
||||
@@ -19,32 +17,28 @@ from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
from app.schemas.workflow import Site as _SchemaSite
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.application.site.mutation import SiteMutationCommand
|
||||
from app.application.site.query import SiteQueryService
|
||||
from app.api.endpoints.plugin import register_plugin_api
|
||||
from app.chain.site import SiteChain
|
||||
from app.chain.torrents import TorrentsChain
|
||||
from app.command import Command
|
||||
from app.runtime.events import eventmanager
|
||||
from app.runtime.extensions.plugin_manager import PluginManager
|
||||
from app.application.security.access import verify_token
|
||||
from app.db import get_db, get_async_db
|
||||
from app.db.models import User
|
||||
from app.db.models.site import Site
|
||||
from app.db.models.siteicon import SiteIcon
|
||||
from app.db.models.sitestatistic import SiteStatistic
|
||||
from app.db.models.siteuserdata import SiteUserData
|
||||
from app.db.oper.site import SiteOper
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.plugin.runtime import get_plugin_manager as PluginManager
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_manage_user_async,
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
get_site_mutation_command,
|
||||
get_site_query_service,
|
||||
get_site_sync_query_service,
|
||||
)
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from app.runtime.log import logger
|
||||
from app.scheduler import Scheduler
|
||||
from app.schemas.types import SystemConfigKey, EventType, MediaType
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.schemas.types import SystemConfigKey, MediaType
|
||||
from app.domain import site as site_rules
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
@@ -88,13 +82,13 @@ def _indexer_supports_media_type(indexer: dict, media_type: MediaType) -> bool:
|
||||
|
||||
@router.get("/", summary="所有站点", response_model=List[_SchemaSite])
|
||||
async def read_sites(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> List[dict]:
|
||||
"""
|
||||
获取站点列表
|
||||
"""
|
||||
return await Site.async_list_order_by_pri(db)
|
||||
return await query.list_ordered()
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -104,14 +98,14 @@ async def read_sites(
|
||||
)
|
||||
async def read_sites_by_media_type(
|
||||
media_type: str,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
) -> List[Site]:
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> List[_SchemaSite]:
|
||||
"""
|
||||
获取支持指定媒体类型的已配置启用站点。
|
||||
|
||||
:param media_type: Agent 媒体类型名称或中文媒体类型
|
||||
:param db: 异步数据库会话
|
||||
:param query: 站点查询服务
|
||||
:return: 按优先级排序的可搜索站点
|
||||
"""
|
||||
target_media_type = MediaType.from_agent(media_type)
|
||||
@@ -134,7 +128,7 @@ async def read_sites_by_media_type(
|
||||
if domain:
|
||||
supported_domains.add(domain)
|
||||
|
||||
sites = await Site.async_list_order_by_pri(db)
|
||||
sites = await query.list_ordered()
|
||||
return [
|
||||
site
|
||||
for site in sites
|
||||
@@ -148,7 +142,7 @@ async def add_site(
|
||||
*,
|
||||
site_in: _SchemaSite,
|
||||
command: SiteMutationCommand = Depends(get_site_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
新增站点
|
||||
@@ -162,7 +156,7 @@ async def update_site(
|
||||
*,
|
||||
site_in: _SchemaSite,
|
||||
command: SiteMutationCommand = Depends(get_site_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
更新站点信息
|
||||
@@ -174,7 +168,7 @@ async def update_site(
|
||||
@router.get("/cookiecloud", summary="CookieCloud同步", response_model=_SchemaResponse[None])
|
||||
async def cookie_cloud_sync(
|
||||
background_tasks: BackgroundTasks,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
运行CookieCloud同步站点信息
|
||||
@@ -184,20 +178,20 @@ async def cookie_cloud_sync(
|
||||
|
||||
|
||||
@router.get("/reset", summary="重置站点", response_model=_SchemaResponse[None])
|
||||
def reset(
|
||||
db: AsyncSession = Depends(get_db), _: User = Depends(get_current_active_superuser)
|
||||
async def reset(
|
||||
command: SiteMutationCommand = Depends(get_site_mutation_command),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
清空所有站点数据并重新同步CookieCloud站点信息
|
||||
"""
|
||||
Site.reset(db)
|
||||
SystemConfigOper().set(SystemConfigKey.IndexerSites, [])
|
||||
SystemConfigOper().set(SystemConfigKey.RssSites, [])
|
||||
result = await command.reset()
|
||||
get_configured_system_config().set(SystemConfigKey.IndexerSites, [])
|
||||
get_configured_system_config().set(SystemConfigKey.RssSites, [])
|
||||
# 启动定时服务
|
||||
Scheduler().start("cookiecloud", manual=True)
|
||||
# 插件站点删除
|
||||
eventmanager.send_event(EventType.SiteDeleted, {"site_id": "*"})
|
||||
return _SchemaResponse(success=True, message="站点已重置!")
|
||||
return _SchemaResponse(success=result.success, message="站点已重置!")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -206,7 +200,7 @@ def reset(
|
||||
async def update_sites_priority(
|
||||
priorities: List[dict],
|
||||
command: SiteMutationCommand = Depends(get_site_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
批量更新站点优先级
|
||||
@@ -220,7 +214,7 @@ def _update_site_cookie(
|
||||
username: str,
|
||||
password: str,
|
||||
code: Optional[str],
|
||||
db: Session,
|
||||
query: SiteQueryService,
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
执行站点 Cookie 与 UA 更新。
|
||||
@@ -229,10 +223,10 @@ def _update_site_cookie(
|
||||
:param username: 站点登录用户名
|
||||
:param password: 站点登录密码
|
||||
:param code: 二步验证码或密钥
|
||||
:param db: 数据库会话
|
||||
:param query: 站点查询服务
|
||||
:return: 更新结果
|
||||
"""
|
||||
site_info = Site.get(db, site_id)
|
||||
site_info = query.get_sync(site_id)
|
||||
if not site_info:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -255,8 +249,8 @@ def _update_site_cookie(
|
||||
def update_cookie_by_body(
|
||||
site_id: int,
|
||||
site_cookie_update: _SchemaSiteCookieUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
query: SiteQueryService = Depends(get_site_sync_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
使用请求体中的用户密码更新站点Cookie
|
||||
@@ -266,7 +260,7 @@ def update_cookie_by_body(
|
||||
username=site_cookie_update.username,
|
||||
password=site_cookie_update.password,
|
||||
code=site_cookie_update.code,
|
||||
db=db,
|
||||
query=query,
|
||||
)
|
||||
|
||||
|
||||
@@ -278,8 +272,8 @@ def update_cookie(
|
||||
username: str,
|
||||
password: str,
|
||||
code: Optional[str] = None,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
query: SiteQueryService = Depends(get_site_sync_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
使用用户密码更新站点Cookie
|
||||
@@ -289,7 +283,7 @@ def update_cookie(
|
||||
username=username,
|
||||
password=password,
|
||||
code=code,
|
||||
db=db,
|
||||
query=query,
|
||||
)
|
||||
|
||||
|
||||
@@ -300,13 +294,13 @@ def update_cookie(
|
||||
)
|
||||
def refresh_userdata(
|
||||
site_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
query: SiteQueryService = Depends(get_site_sync_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
刷新站点用户数据
|
||||
"""
|
||||
site = Site.get(db, site_id)
|
||||
site = query.get_sync(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -327,16 +321,13 @@ def refresh_userdata(
|
||||
response_model=List[_SchemaSiteUserData],
|
||||
)
|
||||
async def read_userdata_latest(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询所有站点最新用户数据
|
||||
"""
|
||||
user_datas = await SiteUserData.async_get_latest(db)
|
||||
if not user_datas:
|
||||
return []
|
||||
return [user_data.to_dict() for user_data in user_datas]
|
||||
return await query.userdata_latest()
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -347,36 +338,34 @@ async def read_userdata_latest(
|
||||
async def read_userdata(
|
||||
site_id: int,
|
||||
workdate: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询站点用户数据
|
||||
"""
|
||||
site = await Site.async_get(db, site_id)
|
||||
site = await query.get(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"站点 {site_id} 不存在",
|
||||
)
|
||||
user_datas = await SiteUserData.async_get_by_domain(
|
||||
db, domain=site.domain, workdate=workdate
|
||||
)
|
||||
user_datas = await query.userdata(site.domain, workdate)
|
||||
if not user_datas:
|
||||
return _SchemaResponse(success=False, data=[])
|
||||
return _SchemaResponse(success=True, data=[data.to_dict() for data in user_datas])
|
||||
return _SchemaResponse(success=True, data=user_datas)
|
||||
|
||||
|
||||
@router.get("/test/{site_id}", summary="连接测试", response_model=_SchemaResponse[None])
|
||||
def test_site(
|
||||
site_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
query: SiteQueryService = Depends(get_site_sync_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
测试站点是否可用
|
||||
"""
|
||||
site = Site.get(db, site_id)
|
||||
site = query.get_sync(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -393,24 +382,22 @@ def test_site(
|
||||
)
|
||||
async def site_icon(
|
||||
site_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
获取站点图标:base64或者url
|
||||
"""
|
||||
site = await Site.async_get(db, site_id)
|
||||
site = await query.get(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"站点 {site_id} 不存在",
|
||||
)
|
||||
icon = await SiteIcon.async_get_by_domain(db, site.domain)
|
||||
icon = await query.icon(site.domain)
|
||||
if not icon:
|
||||
return _SchemaResponse(success=False, message="站点图标不存在!")
|
||||
return _SchemaResponse(
|
||||
success=True, data={"icon": icon.base64 if icon.base64 else icon.url}
|
||||
)
|
||||
return _SchemaResponse(success=True, data=icon.model_dump())
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -418,13 +405,13 @@ async def site_icon(
|
||||
)
|
||||
async def site_category(
|
||||
site_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
获取站点分类
|
||||
"""
|
||||
site = await Site.async_get(db, site_id)
|
||||
site = await query.get(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -456,13 +443,13 @@ async def site_resource(
|
||||
mtype: Optional[str] = None,
|
||||
cat: Optional[str] = None,
|
||||
page: Optional[int] = 0,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
浏览站点资源
|
||||
"""
|
||||
site = await Site.async_get(db, site_id)
|
||||
site = await query.get(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -483,14 +470,14 @@ async def site_resource(
|
||||
@router.get("/domain/{site_url}", summary="站点详情", response_model=_SchemaSite)
|
||||
async def read_site_by_domain(
|
||||
site_url: str,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
通过域名获取站点信息
|
||||
"""
|
||||
domain = site_rules.extract_domain(site_url)
|
||||
site = await Site.async_get_by_domain(db, domain)
|
||||
site = await query.get_by_domain(domain)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -506,45 +493,42 @@ async def read_site_by_domain(
|
||||
)
|
||||
async def read_statistic_by_domain(
|
||||
site_url: str,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
通过域名获取站点统计信息
|
||||
"""
|
||||
domain = site_rules.extract_domain(site_url)
|
||||
sitestatistic = await SiteStatistic.async_get_by_domain(db, domain)
|
||||
if sitestatistic:
|
||||
return sitestatistic
|
||||
return _SchemaSiteStatistic(domain=domain)
|
||||
return await query.statistic(domain)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/statistic", summary="所有站点统计信息", response_model=List[_SchemaSiteStatistic]
|
||||
)
|
||||
async def read_statistics(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""
|
||||
获取所有站点统计信息
|
||||
"""
|
||||
return await SiteStatistic.async_list(db)
|
||||
return await query.statistics()
|
||||
|
||||
|
||||
@router.get("/rss", summary="所有订阅站点", response_model=List[_SchemaSite])
|
||||
async def read_rss_sites(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> List[dict]:
|
||||
"""
|
||||
获取站点列表
|
||||
"""
|
||||
# 选中的rss站点
|
||||
selected_sites = SystemConfigOper().get(SystemConfigKey.RssSites) or []
|
||||
selected_sites = get_configured_system_config().get(SystemConfigKey.RssSites) or []
|
||||
|
||||
# 所有站点
|
||||
all_site = await Site.async_list_order_by_pri(db)
|
||||
all_site = await query.list_ordered()
|
||||
if not selected_sites:
|
||||
return all_site
|
||||
|
||||
@@ -563,7 +547,7 @@ async def read_auth_sites(_: _SchemaTokenPayload = Depends(verify_token)) -> dic
|
||||
|
||||
@router.post("/auth", summary="用户站点认证", response_model=_SchemaResponse[None])
|
||||
def auth_site(
|
||||
auth_info: _SchemaSiteAuth, _: User = Depends(get_current_active_superuser)
|
||||
auth_info: _SchemaSiteAuth, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> Any:
|
||||
"""
|
||||
用户站点认证
|
||||
@@ -571,7 +555,7 @@ def auth_site(
|
||||
if not auth_info or not auth_info.site or not auth_info.params:
|
||||
return _SchemaResponse(success=False, message="请输入认证站点和认证参数")
|
||||
status, msg = SitesHelper().check_user(auth_info.site, auth_info.params)
|
||||
SystemConfigOper().set(SystemConfigKey.UserSiteAuthParams, auth_info.model_dump())
|
||||
get_configured_system_config().set(SystemConfigKey.UserSiteAuthParams, auth_info.model_dump())
|
||||
# 认证成功后,重新初始化插件
|
||||
PluginManager().init_config()
|
||||
Scheduler().init_plugin_jobs()
|
||||
@@ -585,12 +569,15 @@ def auth_site(
|
||||
summary="获取站点域名到名称的映射",
|
||||
response_model=_SchemaResponse[_SchemaSiteMappingData],
|
||||
)
|
||||
async def site_mapping(_: User = Depends(get_current_active_superuser_async)):
|
||||
async def site_mapping(
|
||||
query: SiteQueryService = Depends(get_site_sync_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
):
|
||||
"""
|
||||
获取站点域名到名称的映射关系
|
||||
"""
|
||||
try:
|
||||
sites = await SiteOper().async_list()
|
||||
sites = query.list_sync()
|
||||
mapping = {}
|
||||
for site in sites:
|
||||
mapping[site.domain] = site.name
|
||||
@@ -604,7 +591,7 @@ async def site_mapping(_: User = Depends(get_current_active_superuser_async)):
|
||||
summary="获取支持的站点列表",
|
||||
response_model=_SchemaJsonObject,
|
||||
)
|
||||
async def support_sites(_: User = Depends(get_current_active_superuser_async)):
|
||||
async def support_sites(_: ApiPrincipal = Depends(get_current_active_superuser_async)):
|
||||
"""
|
||||
获取支持的站点列表
|
||||
"""
|
||||
@@ -614,13 +601,13 @@ async def support_sites(_: User = Depends(get_current_active_superuser_async)):
|
||||
@router.get("/{site_id}", summary="站点详情", response_model=_SchemaSite)
|
||||
async def read_site(
|
||||
site_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: SiteQueryService = Depends(get_site_query_service),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
通过ID获取站点信息
|
||||
"""
|
||||
site = await Site.async_get(db, site_id)
|
||||
site = await query.get(site_id)
|
||||
if not site:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -633,7 +620,7 @@ async def read_site(
|
||||
async def delete_site(
|
||||
site_id: int,
|
||||
command: SiteMutationCommand = Depends(get_site_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
删除站点
|
||||
|
||||
@@ -15,7 +15,7 @@ from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import settings
|
||||
from app.db.models import User
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.api.deps import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_superuser,
|
||||
@@ -31,7 +31,7 @@ router = ResponseAPIRouter()
|
||||
"/manage", summary="网盘存储统一管理", response_model=_SchemaResponse[Dict[str, Any]]
|
||||
)
|
||||
def manage(
|
||||
request: _SchemaManageRequest, _: User = Depends(get_current_active_superuser)
|
||||
request: _SchemaManageRequest, _: ApiPrincipal = Depends(get_current_active_superuser)
|
||||
) -> Any:
|
||||
"""
|
||||
网盘存储统一管理入口
|
||||
@@ -56,7 +56,7 @@ def list_files(
|
||||
fileitem: _SchemaFileItem,
|
||||
sort: Optional[str] = "updated_at",
|
||||
keyword: Optional[str] = None,
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
查询当前目录下所有目录和文件
|
||||
@@ -82,7 +82,7 @@ def list_files(
|
||||
def mkdir(
|
||||
fileitem: _SchemaFileItem,
|
||||
name: str,
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
创建目录
|
||||
@@ -100,7 +100,7 @@ def mkdir(
|
||||
|
||||
@router.post("/delete", summary="删除文件或目录", response_model=_SchemaResponse[None])
|
||||
def delete(
|
||||
fileitem: _SchemaFileItem, _: User = Depends(get_current_active_manage_user)
|
||||
fileitem: _SchemaFileItem, _: ApiPrincipal = Depends(get_current_active_manage_user)
|
||||
) -> Any:
|
||||
"""
|
||||
删除文件或目录
|
||||
@@ -131,7 +131,7 @@ def delete(
|
||||
},
|
||||
)
|
||||
def download(
|
||||
fileitem: _SchemaFileItem, _: User = Depends(get_current_active_manage_user)
|
||||
fileitem: _SchemaFileItem, _: ApiPrincipal = Depends(get_current_active_manage_user)
|
||||
) -> Any:
|
||||
"""
|
||||
下载文件或目录
|
||||
@@ -160,7 +160,7 @@ def download(
|
||||
},
|
||||
)
|
||||
def image(
|
||||
fileitem: _SchemaFileItem, _: User = Depends(get_current_active_manage_user)
|
||||
fileitem: _SchemaFileItem, _: ApiPrincipal = Depends(get_current_active_manage_user)
|
||||
) -> Any:
|
||||
"""
|
||||
下载文件或目录
|
||||
@@ -179,7 +179,7 @@ def rename(
|
||||
fileitem: _SchemaFileItem,
|
||||
new_name: str,
|
||||
recursive: Optional[bool] = False,
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: ApiPrincipal = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
重命名文件或目录
|
||||
|
||||
+125
-178
@@ -2,8 +2,6 @@ from typing import List, Any, Annotated, Optional
|
||||
|
||||
import cn2an
|
||||
from fastapi import Request, BackgroundTasks, Depends, HTTPException, Header
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.common import IdData as _SchemaIdData
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
@@ -19,7 +17,7 @@ from app.runtime.config import settings
|
||||
from app.domain.context import MediaInfo
|
||||
from app.runtime.events import eventmanager
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.application.security.access import verify_token, verify_apitoken
|
||||
from app.adapters.web.security.access import verify_token, verify_apitoken
|
||||
from app.application.subscription.delete import (
|
||||
DeleteSubscribeCommand,
|
||||
SubscribeDeletionActor,
|
||||
@@ -31,20 +29,25 @@ from app.application.subscription.search import (
|
||||
SearchSubscriptionsCommand,
|
||||
SubscribeSearchActor,
|
||||
)
|
||||
from app.db import get_async_db, get_db
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.db.models.subscribehistory import SubscribeHistory
|
||||
from app.db.models.user import User
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.subscription.query import SubscriptionQueryService
|
||||
from app.application.subscription.mutation import (
|
||||
SubscriptionActor,
|
||||
SubscriptionMutationService,
|
||||
)
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import (
|
||||
get_current_active_user,
|
||||
get_current_active_user_async,
|
||||
get_delete_subscribe_command,
|
||||
get_delete_subscriptions_by_identity_command,
|
||||
get_search_subscriptions_command,
|
||||
get_subscription_query_service,
|
||||
get_subscription_mutation_service,
|
||||
get_subscription_sync_mutation_service,
|
||||
)
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.scheduler import Scheduler
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.schemas.event import SubscribeModifiedEventData
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
@@ -82,16 +85,15 @@ def start_subscribe_add(
|
||||
)
|
||||
|
||||
|
||||
def build_subscribe_event_payload(subscribe: Subscribe) -> dict:
|
||||
def build_subscribe_event_payload(subscribe: Any) -> dict:
|
||||
"""
|
||||
从 ORM 已加载字段构造订阅事件快照,避免异步接口里属性懒加载触发隐式 IO。
|
||||
"""
|
||||
values = subscribe.__dict__
|
||||
return {column.name: values.get(column.name) for column in subscribe.__table__.columns}
|
||||
return subscribe.to_dict()
|
||||
|
||||
|
||||
def can_access_subscribe(
|
||||
subscribe: Subscribe | SubscribeHistory | None, current_user: User
|
||||
subscribe: Any, current_user: ApiPrincipal
|
||||
) -> bool:
|
||||
"""
|
||||
判断当前用户是否可访问订阅及其历史记录。
|
||||
@@ -107,33 +109,9 @@ def can_access_subscribe(
|
||||
return bool(username) and username == current_user.name
|
||||
|
||||
|
||||
async def get_accessible_subscribe(
|
||||
db: AsyncSession, subscribe_id: int, current_user: User
|
||||
) -> Subscribe | None:
|
||||
"""
|
||||
按订阅 ID 读取当前用户可访问的订阅行。
|
||||
"""
|
||||
subscribe = await Subscribe.async_get(db, subscribe_id)
|
||||
if can_access_subscribe(subscribe, current_user):
|
||||
return subscribe
|
||||
return None
|
||||
|
||||
|
||||
def get_accessible_subscribe_sync(
|
||||
db: Session, subscribe_id: int, current_user: User
|
||||
) -> Subscribe | None:
|
||||
"""
|
||||
同步读取当前用户可访问的订阅行。
|
||||
"""
|
||||
subscribe = Subscribe.get(db, subscribe_id)
|
||||
if can_access_subscribe(subscribe, current_user):
|
||||
return subscribe
|
||||
return None
|
||||
|
||||
|
||||
def select_accessible_subscribe(
|
||||
subscribes: List[Subscribe], current_user: User
|
||||
) -> Subscribe | None:
|
||||
subscribes: List[Any], current_user: ApiPrincipal
|
||||
) -> Any:
|
||||
"""
|
||||
从候选订阅中选择当前用户可访问的第一条记录。
|
||||
"""
|
||||
@@ -144,7 +122,7 @@ def select_accessible_subscribe(
|
||||
|
||||
|
||||
def matches_subscribe_music_type(
|
||||
subscribe: Subscribe,
|
||||
subscribe: Any,
|
||||
music_type: Optional[str],
|
||||
) -> bool:
|
||||
"""匹配订阅音乐实体,并把迁移前未标注类型的历史记录兼容为单曲。"""
|
||||
@@ -155,54 +133,30 @@ def matches_subscribe_music_type(
|
||||
or (music_type == MUSIC_ENTITY_RECORDING and subscribe_music_type is None)
|
||||
|
||||
|
||||
async def list_subscribes_by_media_identity(
|
||||
db: AsyncSession,
|
||||
media_source: MediaSource,
|
||||
media_id: str,
|
||||
season: Optional[int] = None,
|
||||
music_type: Optional[str] = None,
|
||||
) -> List[Subscribe]:
|
||||
"""按媒体来源、原生 ID 及音乐实体查询订阅。"""
|
||||
subscribes = list(await Subscribe.async_list_by_media_identity(
|
||||
db,
|
||||
media_source=media_source,
|
||||
media_id=media_id,
|
||||
music_type=music_type,
|
||||
))
|
||||
unique_subscribes = {
|
||||
subscribe.id: subscribe
|
||||
for subscribe in subscribes
|
||||
if matches_subscribe_music_type(subscribe, music_type)
|
||||
}
|
||||
if season is not None:
|
||||
return [
|
||||
subscribe for subscribe in unique_subscribes.values()
|
||||
if subscribe.season == season
|
||||
]
|
||||
return list(unique_subscribes.values())
|
||||
|
||||
|
||||
@router.get("/", summary="查询所有订阅", response_model=List[_SchemaSubscribe])
|
||||
async def read_subscribes(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
query: SubscriptionQueryService = Depends(get_subscription_query_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询所有订阅
|
||||
"""
|
||||
if not current_user.is_superuser:
|
||||
return await Subscribe.async_list_by_username(db, current_user.name)
|
||||
return await Subscribe.async_list(db)
|
||||
return await query.list_public(current_user.name)
|
||||
return await query.list_public()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/list", summary="查询所有订阅(API_TOKEN)", response_model=List[_SchemaSubscribe]
|
||||
)
|
||||
async def list_subscribes(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
|
||||
async def list_subscribes(
|
||||
query: SubscriptionQueryService = Depends(get_subscription_query_service),
|
||||
_: Annotated[str, Depends(verify_apitoken)] = None,
|
||||
) -> Any:
|
||||
"""
|
||||
查询所有订阅 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
return await Subscribe.async_list()
|
||||
return await query.list_public()
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -213,7 +167,7 @@ async def list_subscribes(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
|
||||
async def create_subscribe(
|
||||
*,
|
||||
subscribe_in: _SchemaSubscribe,
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
新增订阅
|
||||
@@ -274,13 +228,17 @@ async def create_subscribe(
|
||||
async def update_subscribe(
|
||||
*,
|
||||
subscribe_in: _SchemaSubscribe,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
mutation: SubscriptionMutationService = Depends(get_subscription_mutation_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
更新订阅信息
|
||||
"""
|
||||
subscribe = await get_accessible_subscribe(db, subscribe_in.id, current_user)
|
||||
actor = SubscriptionActor(
|
||||
name=current_user.name,
|
||||
is_superuser=current_user.is_superuser,
|
||||
)
|
||||
subscribe = await mutation.get_accessible(subscribe_in.id, actor)
|
||||
if not subscribe:
|
||||
return _SchemaResponse(success=False, message="订阅不存在")
|
||||
old_subscribe_dict = subscribe.to_dict()
|
||||
@@ -326,16 +284,21 @@ async def update_subscribe(
|
||||
if total_episode_updated and subscribe_in.total_episode != subscribe.total_episode:
|
||||
subscribe_dict["manual_total_episode"] = 1
|
||||
# 更新到数据库
|
||||
await subscribe.async_update(db, subscribe_dict)
|
||||
# 重新获取更新后的订阅数据
|
||||
updated_subscribe = await Subscribe.async_get(db, subscribe_in.id)
|
||||
change = await mutation.update(
|
||||
subscribe_in.id,
|
||||
subscribe_dict,
|
||||
actor,
|
||||
existing=subscribe,
|
||||
)
|
||||
if not change:
|
||||
return _SchemaResponse(success=False, message="订阅不存在")
|
||||
# 发送订阅调整事件
|
||||
await eventmanager.async_send_event(
|
||||
EventType.SubscribeModified,
|
||||
SubscribeModifiedEventData(
|
||||
subscribe_id=subscribe_in.id,
|
||||
old_subscribe_info=old_subscribe_dict,
|
||||
subscribe_info=updated_subscribe.to_dict() if updated_subscribe else {},
|
||||
old_subscribe_info=change.old,
|
||||
subscribe_info=change.new,
|
||||
scene="update",
|
||||
).to_dict(),
|
||||
)
|
||||
@@ -346,29 +309,29 @@ async def update_subscribe(
|
||||
async def update_subscribe_status(
|
||||
subid: int,
|
||||
state: str,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
mutation: SubscriptionMutationService = Depends(get_subscription_mutation_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
更新订阅状态
|
||||
"""
|
||||
subscribe = await get_accessible_subscribe(db, subid, current_user)
|
||||
if not subscribe:
|
||||
return _SchemaResponse(success=False, message="订阅不存在")
|
||||
valid_states = ["R", "P", "S"]
|
||||
if state not in valid_states:
|
||||
return _SchemaResponse(success=False, message="无效的订阅状态")
|
||||
old_subscribe_dict = subscribe.to_dict()
|
||||
await subscribe.async_update(db, {"state": state})
|
||||
# 重新获取更新后的订阅数据
|
||||
updated_subscribe = await Subscribe.async_get(db, subid)
|
||||
actor = SubscriptionActor(
|
||||
name=current_user.name,
|
||||
is_superuser=current_user.is_superuser,
|
||||
)
|
||||
change = await mutation.update_status(subid, state, actor)
|
||||
if not change:
|
||||
return _SchemaResponse(success=False, message="订阅不存在")
|
||||
# 发送订阅调整事件
|
||||
await eventmanager.async_send_event(
|
||||
EventType.SubscribeModified,
|
||||
SubscribeModifiedEventData(
|
||||
subscribe_id=subid,
|
||||
old_subscribe_info=old_subscribe_dict,
|
||||
subscribe_info=updated_subscribe.to_dict() if updated_subscribe else {},
|
||||
old_subscribe_info=change.old,
|
||||
subscribe_info=change.new,
|
||||
scene="status",
|
||||
).to_dict(),
|
||||
)
|
||||
@@ -382,22 +345,22 @@ async def subscribe_media_identity(
|
||||
season: Optional[int] = None,
|
||||
title: Optional[str] = None,
|
||||
music_type: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
query: SubscriptionQueryService = Depends(get_subscription_query_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
根据媒体来源和原生 ID 查询订阅。
|
||||
"""
|
||||
subscribes = await list_subscribes_by_media_identity(
|
||||
db, media_source, media_id, season, music_type
|
||||
)
|
||||
subscribes = await query.list_by_media_identity(media_source, media_id, music_type)
|
||||
if season is not None:
|
||||
subscribes = [subscribe for subscribe in subscribes if subscribe.season == season]
|
||||
result = select_accessible_subscribe(subscribes, current_user)
|
||||
return result if result else Subscribe()
|
||||
return result if result else _SchemaSubscribe()
|
||||
|
||||
|
||||
@router.get("/refresh", summary="刷新订阅", response_model=_SchemaResponse[None])
|
||||
def refresh_subscribes(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
刷新所有订阅
|
||||
@@ -411,44 +374,24 @@ def refresh_subscribes(
|
||||
@router.get("/reset/{subid}", summary="重置订阅", response_model=_SchemaResponse[None])
|
||||
async def reset_subscribes(
|
||||
subid: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
mutation: SubscriptionMutationService = Depends(get_subscription_mutation_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
重置订阅
|
||||
"""
|
||||
subscribe = await get_accessible_subscribe(db, subid, current_user)
|
||||
if subscribe:
|
||||
# 在更新之前获取旧数据
|
||||
old_subscribe_dict = subscribe.to_dict()
|
||||
# 更新订阅
|
||||
await subscribe.async_update(
|
||||
db,
|
||||
{
|
||||
"note": [],
|
||||
"lack_episode": subscribe.total_episode,
|
||||
"current_priority": None,
|
||||
"current_audio_format": None,
|
||||
"current_bitrate": None,
|
||||
"current_bit_depth": None,
|
||||
"current_sample_rate": None,
|
||||
"episode_priority": {},
|
||||
# 重置代表放弃手动总集数,后续订阅检查重新按 TMDB 集数更新。
|
||||
"manual_total_episode": 0,
|
||||
"state": "R",
|
||||
},
|
||||
)
|
||||
# 重新获取更新后的订阅数据
|
||||
updated_subscribe = await Subscribe.async_get(db, subid)
|
||||
# 发送订阅调整事件
|
||||
actor = SubscriptionActor(
|
||||
name=current_user.name,
|
||||
is_superuser=current_user.is_superuser,
|
||||
)
|
||||
change = await mutation.reset(subid, actor)
|
||||
if change:
|
||||
await eventmanager.async_send_event(
|
||||
EventType.SubscribeModified,
|
||||
SubscribeModifiedEventData(
|
||||
subscribe_id=subid,
|
||||
old_subscribe_info=old_subscribe_dict,
|
||||
subscribe_info=updated_subscribe.to_dict()
|
||||
if updated_subscribe
|
||||
else {},
|
||||
old_subscribe_info=change.old,
|
||||
subscribe_info=change.new,
|
||||
scene="reset",
|
||||
).to_dict(),
|
||||
)
|
||||
@@ -458,7 +401,7 @@ async def reset_subscribes(
|
||||
|
||||
@router.get("/check", summary="刷新订阅 TMDB 信息", response_model=_SchemaResponse[None])
|
||||
def check_subscribes(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
刷新订阅 TMDB 信息
|
||||
@@ -472,7 +415,7 @@ def check_subscribes(
|
||||
@router.get("/search", summary="搜索所有订阅", response_model=_SchemaResponse[None])
|
||||
async def search_subscribes(
|
||||
command: SearchSubscriptionsCommand = Depends(get_search_subscriptions_command),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
搜索所有订阅
|
||||
@@ -492,7 +435,7 @@ async def search_subscribes(
|
||||
async def search_subscribe(
|
||||
subscribe_id: int,
|
||||
command: SearchSubscriptionsCommand = Depends(get_search_subscriptions_command),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
根据订阅编号搜索订阅
|
||||
@@ -518,7 +461,7 @@ async def delete_subscribe_by_media_identity(
|
||||
command: DeleteSubscriptionsByIdentityCommand = Depends(
|
||||
get_delete_subscriptions_by_identity_command
|
||||
),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
根据任意媒体数据源 ID 删除订阅。
|
||||
@@ -616,28 +559,18 @@ async def subscribe_history(
|
||||
mtype: str,
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 30,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
query: SubscriptionQueryService = Depends(get_subscription_query_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询电影、电视剧或音乐订阅历史
|
||||
"""
|
||||
if current_user.is_superuser:
|
||||
histories = await SubscribeHistory.async_list_by_type(
|
||||
db, mtype=mtype, page=page, count=count
|
||||
)
|
||||
else:
|
||||
histories = await SubscribeHistory.async_list_by_type_and_username(
|
||||
db, mtype=mtype, username=current_user.name, page=page, count=count
|
||||
)
|
||||
result = []
|
||||
for history in histories:
|
||||
history_item = _SchemaSubscribe.model_validate(history, from_attributes=True)
|
||||
if history_item.type == MediaType.TV.value:
|
||||
history_item.total_episode = 0
|
||||
history_item.lack_episode = 0
|
||||
result.append(history_item)
|
||||
return result
|
||||
return await query.list_history(
|
||||
mtype,
|
||||
page=page,
|
||||
count=count,
|
||||
username=None if current_user.is_superuser else current_user.name,
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
@@ -645,15 +578,17 @@ async def subscribe_history(
|
||||
)
|
||||
async def delete_subscribe_history(
|
||||
history_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
mutation: SubscriptionMutationService = Depends(get_subscription_mutation_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
删除订阅历史
|
||||
"""
|
||||
history = await SubscribeHistory.async_get(db, history_id)
|
||||
if can_access_subscribe(history, current_user):
|
||||
await SubscribeHistory.async_delete(db, history_id)
|
||||
actor = SubscriptionActor(
|
||||
name=current_user.name,
|
||||
is_superuser=current_user.is_superuser,
|
||||
)
|
||||
await mutation.delete_history(history_id, actor)
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@@ -721,15 +656,15 @@ async def popular_subscribes(
|
||||
)
|
||||
async def user_subscribes(
|
||||
username: str,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
query: SubscriptionQueryService = Depends(get_subscription_query_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询用户订阅
|
||||
"""
|
||||
if not current_user.is_superuser and username != current_user.name:
|
||||
return []
|
||||
return await Subscribe.async_list_by_username(db, username)
|
||||
return await query.list_public(username)
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -739,13 +674,17 @@ async def user_subscribes(
|
||||
)
|
||||
def subscribe_files(
|
||||
subscribe_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
mutation: SubscriptionMutationService = Depends(get_subscription_sync_mutation_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
订阅相关文件信息
|
||||
"""
|
||||
subscribe = get_accessible_subscribe_sync(db, subscribe_id, current_user)
|
||||
actor = SubscriptionActor(
|
||||
name=current_user.name,
|
||||
is_superuser=current_user.is_superuser,
|
||||
)
|
||||
subscribe = mutation.get_accessible_sync(subscribe_id, actor)
|
||||
if subscribe:
|
||||
return SubscribeChain().subscribe_files_info(subscribe)
|
||||
return _SchemaSubscrbieInfo()
|
||||
@@ -754,13 +693,17 @@ def subscribe_files(
|
||||
@router.post("/share", summary="分享订阅", response_model=_SchemaResponse[None])
|
||||
async def subscribe_share(
|
||||
sub: _SchemaSubscribeShare,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
mutation: SubscriptionMutationService = Depends(get_subscription_mutation_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
分享订阅
|
||||
"""
|
||||
subscribe = await get_accessible_subscribe(db, sub.subscribe_id, current_user)
|
||||
actor = SubscriptionActor(
|
||||
name=current_user.name,
|
||||
is_superuser=current_user.is_superuser,
|
||||
)
|
||||
subscribe = await mutation.get_accessible(sub.subscribe_id, actor)
|
||||
if not subscribe:
|
||||
return _SchemaResponse(success=False, message="订阅不存在")
|
||||
state, errmsg = await MoviePilotServerHelper.async_sub_share(
|
||||
@@ -786,7 +729,7 @@ async def subscribe_share_delete(
|
||||
@router.post("/fork", summary="复用订阅", response_model=_SchemaResponse[None])
|
||||
async def subscribe_fork(
|
||||
sub: _SchemaSubscribeShare,
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
复用订阅
|
||||
@@ -809,7 +752,7 @@ async def followed_subscribers(_: _SchemaTokenPayload = Depends(verify_token)) -
|
||||
"""
|
||||
查询已Follow的订阅分享人
|
||||
"""
|
||||
return SystemConfigOper().get(SystemConfigKey.FollowSubscribers) or []
|
||||
return get_configured_system_config().get(SystemConfigKey.FollowSubscribers) or []
|
||||
|
||||
|
||||
@router.post("/follow", summary="Follow订阅分享人", response_model=_SchemaResponse[None])
|
||||
@@ -819,10 +762,10 @@ async def follow_subscriber(
|
||||
"""
|
||||
Follow订阅分享人
|
||||
"""
|
||||
subscribers = SystemConfigOper().get(SystemConfigKey.FollowSubscribers) or []
|
||||
subscribers = get_configured_system_config().get(SystemConfigKey.FollowSubscribers) or []
|
||||
if share_uid and share_uid not in subscribers:
|
||||
subscribers.append(share_uid)
|
||||
await SystemConfigOper().async_set(
|
||||
await get_configured_system_config().async_set(
|
||||
SystemConfigKey.FollowSubscribers, subscribers
|
||||
)
|
||||
return _SchemaResponse(success=True)
|
||||
@@ -837,10 +780,10 @@ async def unfollow_subscriber(
|
||||
"""
|
||||
取消Follow订阅分享人
|
||||
"""
|
||||
subscribers = SystemConfigOper().get(SystemConfigKey.FollowSubscribers) or []
|
||||
subscribers = get_configured_system_config().get(SystemConfigKey.FollowSubscribers) or []
|
||||
if share_uid and share_uid in subscribers:
|
||||
subscribers.remove(share_uid)
|
||||
await SystemConfigOper().async_set(
|
||||
await get_configured_system_config().async_set(
|
||||
SystemConfigKey.FollowSubscribers, subscribers
|
||||
)
|
||||
return _SchemaResponse(success=True)
|
||||
@@ -891,23 +834,27 @@ async def subscribe_share_statistics(
|
||||
@router.get("/{subscribe_id}", summary="订阅详情", response_model=_SchemaSubscribe)
|
||||
async def read_subscribe(
|
||||
subscribe_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
query: SubscriptionQueryService = Depends(get_subscription_query_service),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
根据订阅编号查询订阅信息
|
||||
"""
|
||||
if not subscribe_id:
|
||||
return Subscribe()
|
||||
subscribe = await get_accessible_subscribe(db, subscribe_id, current_user)
|
||||
return subscribe if subscribe else Subscribe()
|
||||
return _SchemaSubscribe()
|
||||
subscribe = await query.get_public(subscribe_id)
|
||||
return (
|
||||
subscribe
|
||||
if subscribe and can_access_subscribe(subscribe, current_user)
|
||||
else _SchemaSubscribe()
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{subscribe_id}", summary="删除订阅", response_model=_SchemaResponse[None])
|
||||
async def delete_subscribe(
|
||||
subscribe_id: int,
|
||||
command: DeleteSubscribeCommand = Depends(get_delete_subscribe_command),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: ApiPrincipal = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
删除订阅信息
|
||||
|
||||
+20
-20
@@ -38,10 +38,10 @@ from app.chain.system import SystemChain
|
||||
from app.runtime.config import global_vars, settings
|
||||
from app.runtime.events import eventmanager
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.runtime.extensions.module_manager import ModuleManager
|
||||
from app.application.security.access import verify_apitoken, verify_resource_token, verify_token
|
||||
from app.db.models import User
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.module import ModuleManager
|
||||
from app.adapters.web.security.access import verify_apitoken, verify_resource_token, verify_token
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import get_current_active_superuser, get_current_active_superuser_async, get_current_active_user_async
|
||||
from app.application.image import ImageHelper
|
||||
from app.runtime.localization import LocaleHelper
|
||||
@@ -57,7 +57,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.scheduler import Scheduler
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.schemas.event import ConfigChangeEventData
|
||||
from app.schemas.types import SystemConfigKey, EventType
|
||||
from app.foundation.crypto import HashUtils
|
||||
@@ -704,7 +704,7 @@ def get_global_setting(token: str):
|
||||
summary="查询用户相关系统设置",
|
||||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||||
)
|
||||
async def get_user_global_setting(_: User = Depends(get_current_active_user_async)):
|
||||
async def get_user_global_setting(_: ApiPrincipal = Depends(get_current_active_user_async)):
|
||||
"""
|
||||
查询用户相关系统设置(登录后获取)
|
||||
包含业务功能相关的配置和用户权限信息
|
||||
@@ -745,7 +745,7 @@ async def get_user_global_setting(_: User = Depends(get_current_active_user_asyn
|
||||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||||
)
|
||||
async def get_env_setting(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
查询系统环境变量,包括当前版本号(仅管理员)
|
||||
@@ -769,7 +769,7 @@ async def get_env_setting(
|
||||
summary="查询安装版本统计报表",
|
||||
response_model=_SchemaResponse[_SchemaJsonObject],
|
||||
)
|
||||
async def usage_statistic(_: User = Depends(get_current_active_user_async)):
|
||||
async def usage_statistic(_: ApiPrincipal = Depends(get_current_active_user_async)):
|
||||
"""
|
||||
查询安装版本统计报表
|
||||
"""
|
||||
@@ -777,7 +777,7 @@ async def usage_statistic(_: User = Depends(get_current_active_user_async)):
|
||||
|
||||
|
||||
@router.get("/ping", summary="服务存活检测", response_model=_SchemaResponse[None])
|
||||
async def ping(_: User = Depends(get_current_active_user_async)) -> _SchemaResponse:
|
||||
async def ping(_: ApiPrincipal = Depends(get_current_active_user_async)) -> _SchemaResponse:
|
||||
"""
|
||||
检测服务是否可用
|
||||
"""
|
||||
@@ -790,7 +790,7 @@ async def ping(_: User = Depends(get_current_active_user_async)) -> _SchemaRespo
|
||||
response_model=_SchemaResponse[_SchemaSystemEnvironmentUpdateData],
|
||||
)
|
||||
async def set_env_setting(
|
||||
env: dict, _: User = Depends(get_current_active_superuser_async)
|
||||
env: dict, _: ApiPrincipal = Depends(get_current_active_superuser_async)
|
||||
):
|
||||
"""
|
||||
更新系统环境变量(仅管理员)
|
||||
@@ -870,7 +870,7 @@ async def get_progress(
|
||||
response_model=_SchemaResponse[_SchemaValueData],
|
||||
)
|
||||
async def get_public_setting(
|
||||
key: str, _: User = Depends(get_current_active_user_async)
|
||||
key: str, _: ApiPrincipal = Depends(get_current_active_user_async)
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
查询普通用户可读取的非敏感系统设置
|
||||
@@ -879,7 +879,7 @@ async def get_public_setting(
|
||||
return _SchemaResponse(success=True, data={"value": getattr(settings, key)})
|
||||
if key not in _PUBLIC_SYSTEM_CONFIG_KEYS:
|
||||
raise HTTPException(status_code=404, detail="配置项不存在")
|
||||
value = SystemConfigOper().get(_PUBLIC_SYSTEM_CONFIG_KEYS[key])
|
||||
value = get_configured_system_config().get(_PUBLIC_SYSTEM_CONFIG_KEYS[key])
|
||||
return _SchemaResponse(success=True, data={"value": value})
|
||||
|
||||
|
||||
@@ -890,7 +890,7 @@ async def get_public_setting(
|
||||
)
|
||||
async def sync_plugin_market_from_wiki(
|
||||
request: Optional[_SchemaPluginMarketSyncRequest] = Body(default=None),
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
从 Wiki 插件文档同步插件市场仓库地址。
|
||||
@@ -956,7 +956,7 @@ async def sync_plugin_market_from_wiki(
|
||||
response_model=_SchemaResponse[_SchemaValueData],
|
||||
)
|
||||
async def get_setting(
|
||||
key: str, _: User = Depends(get_current_active_superuser_async)
|
||||
key: str, _: ApiPrincipal = Depends(get_current_active_superuser_async)
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
查询系统设置(仅管理员)
|
||||
@@ -964,7 +964,7 @@ async def get_setting(
|
||||
if hasattr(settings, key):
|
||||
value = getattr(settings, key)
|
||||
else:
|
||||
value = SystemConfigOper().get(key)
|
||||
value = get_configured_system_config().get(key)
|
||||
return _SchemaResponse(success=True, data={"value": value})
|
||||
|
||||
|
||||
@@ -972,7 +972,7 @@ async def get_setting(
|
||||
async def set_setting(
|
||||
key: str,
|
||||
value: Annotated[Union[list, dict, bool, int, str] | None, Body()] = None,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser_async),
|
||||
):
|
||||
"""
|
||||
更新系统设置(仅管理员)
|
||||
@@ -992,7 +992,7 @@ async def set_setting(
|
||||
if isinstance(value, list):
|
||||
value = list(filter(None, value))
|
||||
value = value if value else None
|
||||
success = await SystemConfigOper().async_set(key, value)
|
||||
success = await get_configured_system_config().async_set(key, value)
|
||||
if success:
|
||||
# 发送配置变更事件
|
||||
await eventmanager.async_send_event(
|
||||
@@ -1446,7 +1446,7 @@ def moduletest(moduleid: str, _: _SchemaTokenPayload = Depends(verify_token)):
|
||||
|
||||
|
||||
@router.get("/restart", summary="重启系统", response_model=_SchemaResponse[None])
|
||||
def restart_system(_: User = Depends(get_current_active_superuser)):
|
||||
def restart_system(_: ApiPrincipal = Depends(get_current_active_superuser)):
|
||||
"""
|
||||
重启系统(仅管理员)
|
||||
"""
|
||||
@@ -1459,7 +1459,7 @@ def restart_system(_: User = Depends(get_current_active_superuser)):
|
||||
@router.post("/upgrade", summary="升级并重启系统", response_model=_SchemaResponse[None])
|
||||
def upgrade_system(
|
||||
mode: Annotated[str | None, Body()] = None,
|
||||
_: User = Depends(get_current_active_superuser),
|
||||
_: ApiPrincipal = Depends(get_current_active_superuser),
|
||||
):
|
||||
"""
|
||||
触发系统升级并重启(仅管理员)
|
||||
@@ -1475,7 +1475,7 @@ def upgrade_system(
|
||||
|
||||
|
||||
@router.get("/runscheduler", summary="运行服务", response_model=_SchemaResponse[None])
|
||||
def run_scheduler(jobid: str, _: User = Depends(get_current_active_superuser)):
|
||||
def run_scheduler(jobid: str, _: ApiPrincipal = Depends(get_current_active_superuser)):
|
||||
"""
|
||||
执行命令(仅管理员)
|
||||
"""
|
||||
|
||||
@@ -12,9 +12,8 @@ from app.schemas.workflow import MediaInfo as _SchemaMediaInfo
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.runtime.config import settings
|
||||
from app.application.security.access import verify_token
|
||||
from app.db.models.user import User
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import get_current_active_superuser_async
|
||||
from app.schemas.types import MediaType, SystemConfigKey
|
||||
|
||||
@@ -27,7 +26,7 @@ router = ResponseAPIRouter()
|
||||
response_model=_SchemaResponse[_SchemaTmdbRecognitionCacheData],
|
||||
)
|
||||
async def tmdb_recognition_cache(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""查询可管理的 TheMovieDb 识别缓存。"""
|
||||
cache_items = TmdbChain().cache_items()
|
||||
@@ -38,7 +37,7 @@ async def tmdb_recognition_cache(
|
||||
"count": len(cache_items),
|
||||
"recognized": recognized_count,
|
||||
"unrecognized": len(cache_items) - recognized_count,
|
||||
"shared_recognized": SystemConfigOper().get(
|
||||
"shared_recognized": get_configured_system_config().get(
|
||||
SystemConfigKey.MediaRecognizeShareCount
|
||||
) or 0,
|
||||
"shared_recognize_enabled": settings.MEDIA_RECOGNIZE_SHARE,
|
||||
@@ -54,7 +53,7 @@ async def tmdb_recognition_cache(
|
||||
)
|
||||
async def delete_tmdb_recognition_cache(
|
||||
cache_key: str,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""按缓存键删除单条 TheMovieDb 识别缓存。"""
|
||||
deleted_item = TmdbChain().delete_cache(cache_key)
|
||||
@@ -67,7 +66,7 @@ async def delete_tmdb_recognition_cache(
|
||||
"/cache", summary="清空 TheMovieDb 识别缓存", response_model=_SchemaResponse[None]
|
||||
)
|
||||
async def clear_tmdb_recognition_cache(
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
) -> _SchemaResponse:
|
||||
"""清空全部 TheMovieDb 识别缓存。"""
|
||||
TmdbChain().clear_cache()
|
||||
|
||||
@@ -12,7 +12,6 @@ from app.runtime.config import settings
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.db.models import User
|
||||
from app.api.deps import get_current_active_superuser, get_current_active_superuser_async
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
@@ -32,7 +31,7 @@ router = ResponseAPIRouter()
|
||||
summary="获取种子缓存",
|
||||
response_model=_SchemaResponse[_SchemaTorrentCacheData],
|
||||
)
|
||||
async def torrents_cache(_: User = Depends(get_current_active_superuser_async)):
|
||||
async def torrents_cache(_: object = Depends(get_current_active_superuser_async)):
|
||||
"""
|
||||
获取当前种子缓存数据
|
||||
"""
|
||||
@@ -103,7 +102,7 @@ async def torrents_cache(_: User = Depends(get_current_active_superuser_async)):
|
||||
async def delete_cache(
|
||||
domain: str,
|
||||
torrent_hash: str,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
):
|
||||
"""
|
||||
删除指定的种子缓存
|
||||
@@ -147,7 +146,7 @@ async def delete_cache(
|
||||
|
||||
|
||||
@router.delete("/cache", summary="清理种子缓存", response_model=_SchemaResponse[None])
|
||||
async def clear_cache(_: User = Depends(get_current_active_superuser_async)):
|
||||
async def clear_cache(_: object = Depends(get_current_active_superuser_async)):
|
||||
"""
|
||||
清理所有种子缓存
|
||||
"""
|
||||
@@ -161,7 +160,7 @@ async def clear_cache(_: User = Depends(get_current_active_superuser_async)):
|
||||
|
||||
|
||||
@router.post("/cache/refresh", summary="刷新种子缓存", response_model=_SchemaResponse[None])
|
||||
def refresh_cache(_: User = Depends(get_current_active_superuser)):
|
||||
def refresh_cache(_: object = Depends(get_current_active_superuser)):
|
||||
"""
|
||||
刷新种子缓存
|
||||
"""
|
||||
@@ -195,7 +194,7 @@ async def reidentify_cache(
|
||||
media_source: Optional[MediaSource] = None,
|
||||
media_id: Optional[str] = None,
|
||||
music_type: Optional[MusicTargetEntityType] = None,
|
||||
_: User = Depends(get_current_active_superuser_async),
|
||||
_: object = Depends(get_current_active_superuser_async),
|
||||
):
|
||||
"""
|
||||
重新识别指定的种子
|
||||
|
||||
@@ -2,7 +2,6 @@ from pathlib import Path
|
||||
from typing import Any, List, Annotated, Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.common import NameData as _SchemaNameData
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
@@ -19,12 +18,13 @@ from app.api.response import ResponseAPIRouter
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.application.security.access import verify_token, verify_apitoken
|
||||
from app.db import get_db
|
||||
from app.db.models import User
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.api.deps import get_current_active_manage_user
|
||||
from app.adapters.web.security.access import verify_token, verify_apitoken
|
||||
from app.api.deps import (
|
||||
get_current_active_manage_user,
|
||||
get_transfer_history_lookup_service,
|
||||
)
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.history import TransferHistoryLookupService
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.workflow import FileItem
|
||||
@@ -106,7 +106,8 @@ async def remove_queue(
|
||||
|
||||
|
||||
def _resolve_manual_transfer_source_fileitems(
|
||||
transer_item: ManualTransferItem, db: Session
|
||||
transer_item: ManualTransferItem,
|
||||
history_query: TransferHistoryLookupService,
|
||||
) -> tuple[List[FileItem], Optional[str]]:
|
||||
"""
|
||||
从手动整理请求中解析源文件项。
|
||||
@@ -114,7 +115,7 @@ def _resolve_manual_transfer_source_fileitems(
|
||||
if transer_item.logids:
|
||||
fileitems: List[FileItem] = []
|
||||
for logid in transer_item.logids:
|
||||
history: TransferHistory = TransferHistory.get(db, logid)
|
||||
history = history_query.get(logid)
|
||||
if not history:
|
||||
return [], f"整理记录不存在,ID:{logid}"
|
||||
if history.status and ("move" in history.mode):
|
||||
@@ -124,7 +125,7 @@ def _resolve_manual_transfer_source_fileitems(
|
||||
return fileitems, None
|
||||
|
||||
if transer_item.logid:
|
||||
history: TransferHistory = TransferHistory.get(db, transer_item.logid)
|
||||
history = history_query.get(transer_item.logid)
|
||||
if not history:
|
||||
return [], f"整理记录不存在,ID:{transer_item.logid}"
|
||||
if history.status and ("move" in history.mode):
|
||||
@@ -195,19 +196,21 @@ def _get_manual_transfer_target_key(
|
||||
)
|
||||
def match_manual_transfer_target_path(
|
||||
transer_item: ManualTransferItem,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
history_query: TransferHistoryLookupService = Depends(
|
||||
get_transfer_history_lookup_service
|
||||
),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
根据源文件匹配手动整理目的路径。
|
||||
|
||||
:param transer_item: 手工整理项
|
||||
:param db: 数据库
|
||||
:param history_query: 整理历史投影服务
|
||||
:param _: Token校验
|
||||
"""
|
||||
src_fileitems, error_message = _resolve_manual_transfer_source_fileitems(
|
||||
transer_item=transer_item,
|
||||
db=db,
|
||||
history_query=history_query,
|
||||
)
|
||||
if error_message:
|
||||
return _SchemaResponse(success=False, message=error_message)
|
||||
@@ -258,19 +261,21 @@ def match_manual_transfer_target_path(
|
||||
)
|
||||
def query_manual_transfer_history(
|
||||
transer_item: ManualTransferItem,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
history_query: TransferHistoryLookupService = Depends(
|
||||
get_transfer_history_lookup_service
|
||||
),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
查询文件或目录命中的成功整理记录。
|
||||
|
||||
:param transer_item: 手工整理项
|
||||
:param db: 数据库
|
||||
:param history_query: 整理历史投影服务
|
||||
:param _: Token校验
|
||||
"""
|
||||
src_fileitems, error_message = _resolve_manual_transfer_source_fileitems(
|
||||
transer_item=transer_item,
|
||||
db=db,
|
||||
history_query=history_query,
|
||||
)
|
||||
if error_message:
|
||||
return _SchemaResponse(success=False, message=error_message)
|
||||
@@ -293,14 +298,16 @@ def query_manual_transfer_history(
|
||||
def manual_transfer(
|
||||
transer_item: ManualTransferItem,
|
||||
background: Optional[bool] = False,
|
||||
db: Session = Depends(get_db),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
history_query: TransferHistoryLookupService = Depends(
|
||||
get_transfer_history_lookup_service
|
||||
),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
手动转移,文件或历史记录,支持自定义剧集识别格式
|
||||
:param transer_item: 手工整理项
|
||||
:param background: 后台运行
|
||||
:param db: 数据库
|
||||
:param history_query: 整理历史投影服务
|
||||
:param _: Token校验
|
||||
"""
|
||||
force = False
|
||||
@@ -311,7 +318,7 @@ def manual_transfer(
|
||||
target_path = Path(transer_item.target_path) if transer_item.target_path else None
|
||||
if transer_item.logid:
|
||||
# 查询历史记录
|
||||
history: TransferHistory = TransferHistory.get(db, transer_item.logid)
|
||||
history = history_query.get(transer_item.logid)
|
||||
if not history:
|
||||
return _SchemaResponse(
|
||||
success=False, message=f"整理记录不存在,ID:{transer_item.logid}"
|
||||
@@ -592,7 +599,7 @@ def manual_transfer(
|
||||
)
|
||||
def recommend_episode_format(
|
||||
recommend_item: EpisodeFormatRecommendItem,
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
根据目录样本推荐集数定位模板
|
||||
|
||||
+45
-38
@@ -3,7 +3,6 @@ import re
|
||||
from typing import Annotated, Any, List, Union
|
||||
|
||||
from fastapi import Body, Depends, HTTPException, UploadFile, File
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.schemas.common import FileNameData as _SchemaFileNameData
|
||||
from app.schemas.common import ValueData as _SchemaValueData
|
||||
@@ -12,37 +11,41 @@ from app.schemas.user import User as _SchemaUser
|
||||
from app.schemas.user import UserCreate as _SchemaUserCreate
|
||||
from app.schemas.user import UserUpdate as _SchemaUserUpdate
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.application.security.access import PasswordTooLongError, get_password_hash
|
||||
from app.db import get_async_db
|
||||
from app.db.models.user import User
|
||||
from app.api.deps import get_current_active_superuser_async, get_current_active_user_async, get_current_active_user
|
||||
from app.db.oper.userconfig import UserConfigOper
|
||||
from app.application.security.token import PasswordTooLongError, get_password_hash
|
||||
from app.application.security.user import UserService
|
||||
from app.api.deps import (
|
||||
get_current_active_superuser_async,
|
||||
get_current_active_user_async,
|
||||
get_current_active_user,
|
||||
get_user_service,
|
||||
)
|
||||
from app.application.security.userconfig import get_configured_user_configuration
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@router.get("/", summary="所有用户", response_model=List[_SchemaUser])
|
||||
async def list_users(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: User = Depends(get_current_active_superuser_async),
|
||||
service: UserService = Depends(get_user_service),
|
||||
current_user: Any = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询用户列表
|
||||
"""
|
||||
return await current_user.async_list(db)
|
||||
return await service.list()
|
||||
|
||||
|
||||
@router.post("/", summary="新增用户", response_model=_SchemaResponse[None])
|
||||
async def create_user(
|
||||
*,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: UserService = Depends(get_user_service),
|
||||
user_in: _SchemaUserCreate,
|
||||
current_user: User = Depends(get_current_active_superuser_async),
|
||||
current_user: Any = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
新增用户
|
||||
"""
|
||||
user = await current_user.async_get_by_name(db, name=user_in.name)
|
||||
user = await service.get_by_name(user_in.name)
|
||||
if user:
|
||||
return _SchemaResponse(success=False, message="用户已存在")
|
||||
user_info = user_in.model_dump()
|
||||
@@ -52,16 +55,16 @@ async def create_user(
|
||||
except PasswordTooLongError as error:
|
||||
return _SchemaResponse(success=False, message=str(error))
|
||||
user_info.pop("password")
|
||||
user = await User(**user_info).async_create(db)
|
||||
user = await service.create(user_info)
|
||||
return _SchemaResponse(success=True if user else False)
|
||||
|
||||
|
||||
@router.put("/", summary="更新用户", response_model=_SchemaResponse[None])
|
||||
async def update_user(
|
||||
*,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: UserService = Depends(get_user_service),
|
||||
user_in: _SchemaUserUpdate,
|
||||
current_user: User = Depends(get_current_active_superuser_async),
|
||||
current_user: Any = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
更新用户
|
||||
@@ -80,24 +83,24 @@ async def update_user(
|
||||
except PasswordTooLongError as error:
|
||||
return _SchemaResponse(success=False, message=str(error))
|
||||
user_info.pop("password")
|
||||
user = await current_user.async_get_by_id(db, user_id=user_info["id"])
|
||||
user = await service.get_by_id(user_info["id"])
|
||||
user_name = user_info.get("name")
|
||||
if not user_name:
|
||||
return _SchemaResponse(success=False, message="用户名不能为空")
|
||||
# 新用户名去重
|
||||
users = await current_user.async_list(db)
|
||||
users = await service.list()
|
||||
for u in users:
|
||||
if u.name == user_name and u.id != user_info["id"]:
|
||||
return _SchemaResponse(success=False, message="用户名已被使用")
|
||||
if not user:
|
||||
return _SchemaResponse(success=False, message="用户不存在")
|
||||
await user.async_update(db, user_info)
|
||||
await service.update(user_info["id"], user_info)
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@router.get("/current", summary="当前登录用户信息", response_model=_SchemaUser)
|
||||
async def read_current_user(
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: Any = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
当前登录用户信息
|
||||
@@ -112,9 +115,9 @@ async def read_current_user(
|
||||
)
|
||||
async def upload_avatar(
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: UserService = Depends(get_user_service),
|
||||
file: UploadFile = File(...),
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
current_user: Any = Depends(get_current_active_user_async),
|
||||
) -> _SchemaResponse:
|
||||
"""
|
||||
上传用户头像
|
||||
@@ -125,10 +128,10 @@ async def upload_avatar(
|
||||
# 将文件转换为Base64
|
||||
file_base64 = base64.b64encode(file.file.read())
|
||||
# 更新到用户表
|
||||
user = await User.async_get(db, user_id)
|
||||
user = await service.get_by_id(user_id)
|
||||
if not user:
|
||||
return _SchemaResponse(success=False, message="用户不存在")
|
||||
await user.async_update(db, {"avatar": f"data:image/ico;base64,{file_base64}"})
|
||||
await service.update(user_id, {"avatar": f"data:image/ico;base64,{file_base64}"})
|
||||
return _SchemaResponse(success=True, data={"filename": file.filename})
|
||||
|
||||
|
||||
@@ -137,11 +140,11 @@ async def upload_avatar(
|
||||
summary="查询用户配置",
|
||||
response_model=_SchemaResponse[_SchemaValueData],
|
||||
)
|
||||
def get_config(key: str, current_user: User = Depends(get_current_active_user)):
|
||||
def get_config(key: str, current_user: Any = Depends(get_current_active_user)):
|
||||
"""
|
||||
查询用户配置
|
||||
"""
|
||||
value = UserConfigOper().get(username=current_user.name, key=key)
|
||||
value = get_configured_user_configuration().get(username=current_user.name, key=key)
|
||||
return _SchemaResponse(success=True, data={"value": value})
|
||||
|
||||
|
||||
@@ -149,59 +152,63 @@ def get_config(key: str, current_user: User = Depends(get_current_active_user)):
|
||||
def set_config(
|
||||
key: str,
|
||||
value: Annotated[Union[list, dict, bool, int, str] | None, Body()] = None,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
current_user: Any = Depends(get_current_active_user),
|
||||
):
|
||||
"""
|
||||
更新用户配置
|
||||
"""
|
||||
UserConfigOper().set(username=current_user.name, key=key, value=value)
|
||||
get_configured_user_configuration().set(
|
||||
username=current_user.name,
|
||||
key=key,
|
||||
value=value,
|
||||
)
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@router.delete("/id/{user_id}", summary="删除用户", response_model=_SchemaResponse[None])
|
||||
async def delete_user_by_id(
|
||||
*,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: UserService = Depends(get_user_service),
|
||||
user_id: int,
|
||||
current_user: User = Depends(get_current_active_superuser_async),
|
||||
current_user: Any = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
通过唯一ID删除用户
|
||||
"""
|
||||
user = await current_user.async_get_by_id(db, user_id=user_id)
|
||||
user = await service.get_by_id(user_id)
|
||||
if not user:
|
||||
return _SchemaResponse(success=False, message="用户不存在")
|
||||
await current_user.async_delete(db, user_id)
|
||||
await service.delete(user_id)
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@router.delete("/name/{user_name}", summary="删除用户", response_model=_SchemaResponse[None])
|
||||
async def delete_user_by_name(
|
||||
*,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
service: UserService = Depends(get_user_service),
|
||||
user_name: str,
|
||||
current_user: User = Depends(get_current_active_superuser_async),
|
||||
current_user: Any = Depends(get_current_active_superuser_async),
|
||||
) -> Any:
|
||||
"""
|
||||
通过用户名删除用户
|
||||
"""
|
||||
user = await current_user.async_get_by_name(db, name=user_name)
|
||||
user = await service.get_by_name(user_name)
|
||||
if not user:
|
||||
return _SchemaResponse(success=False, message="用户不存在")
|
||||
await current_user.async_delete(db, user.id)
|
||||
await service.delete(user.id)
|
||||
return _SchemaResponse(success=True)
|
||||
|
||||
|
||||
@router.get("/{username}", summary="用户详情", response_model=_SchemaUser)
|
||||
async def read_user_by_name(
|
||||
username: str,
|
||||
current_user: User = Depends(get_current_active_user_async),
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
current_user: Any = Depends(get_current_active_user_async),
|
||||
service: UserService = Depends(get_user_service),
|
||||
) -> Any:
|
||||
"""
|
||||
查询用户详情
|
||||
"""
|
||||
user = await current_user.async_get_by_name(db, name=username)
|
||||
user = await service.get_by_name(username)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi import BackgroundTasks, Request, Depends
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.chain.webhook import WebhookChain
|
||||
from app.application.security.access import verify_apitoken
|
||||
from app.adapters.web.security.access import verify_apitoken
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import List, Any, Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.schemas.workflow import NameValueOption as _SchemaNameValueOption
|
||||
@@ -10,19 +9,21 @@ from app.schemas.workflow import Workflow as _SchemaWorkflow
|
||||
from app.schemas.workflow import WorkflowActionDefinition as _SchemaWorkflowActionDefinition
|
||||
from app.schemas.workflow import WorkflowShare as _SchemaWorkflowShare
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.application.workflow import WorkflowDefinitionCommand, WorkflowMutationCommand
|
||||
from app.application.workflow import (
|
||||
WorkflowDefinitionCommand,
|
||||
WorkflowMutationCommand,
|
||||
WorkflowQueryService,
|
||||
)
|
||||
from app.chain.workflow import WorkflowChain
|
||||
from app.runtime.extensions.plugin_manager import PluginManager
|
||||
from app.application.plugin.runtime import get_plugin_manager as PluginManager
|
||||
from app.workflow import WorkFlowManager
|
||||
from app.db import get_async_db
|
||||
from app.db.models import User
|
||||
from app.api.deps import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_manage_user_async,
|
||||
get_workflow_definition_command,
|
||||
get_workflow_mutation_command,
|
||||
get_workflow_query_service,
|
||||
)
|
||||
from app.db.oper.workflow import WorkflowOper
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.schemas.types import EventType, EVENT_TYPE_NAMES
|
||||
|
||||
@@ -30,20 +31,20 @@ router = ResponseAPIRouter()
|
||||
|
||||
@router.get("/", summary="所有工作流", response_model=List[_SchemaWorkflow])
|
||||
async def list_workflows(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: WorkflowQueryService = Depends(get_workflow_query_service),
|
||||
_: Any = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
获取工作流列表
|
||||
"""
|
||||
return await WorkflowOper(db).async_list()
|
||||
return await query.list()
|
||||
|
||||
|
||||
@router.post("/", summary="创建工作流", response_model=_SchemaResponse[None])
|
||||
async def create_workflow(
|
||||
workflow: _SchemaWorkflow,
|
||||
command: WorkflowDefinitionCommand = Depends(get_workflow_definition_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: Any = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
创建工作流
|
||||
@@ -58,7 +59,7 @@ async def create_workflow(
|
||||
response_model=List[_SchemaPluginWorkflowActionGroup],
|
||||
)
|
||||
def list_plugin_actions(
|
||||
plugin_id: str = None, _: User = Depends(get_current_active_manage_user)
|
||||
plugin_id: str = None, _: Any = Depends(get_current_active_manage_user)
|
||||
) -> Any:
|
||||
"""
|
||||
获取所有动作
|
||||
@@ -71,7 +72,7 @@ def list_plugin_actions(
|
||||
summary="所有动作",
|
||||
response_model=List[_SchemaWorkflowActionDefinition],
|
||||
)
|
||||
async def list_actions(_: User = Depends(get_current_active_manage_user_async)) -> Any:
|
||||
async def list_actions(_: Any = Depends(get_current_active_manage_user_async)) -> Any:
|
||||
"""
|
||||
获取所有动作
|
||||
"""
|
||||
@@ -83,7 +84,7 @@ async def list_actions(_: User = Depends(get_current_active_manage_user_async))
|
||||
summary="获取所有事件类型",
|
||||
response_model=List[_SchemaNameValueOption],
|
||||
)
|
||||
async def get_event_types(_: User = Depends(get_current_active_manage_user_async)) -> Any:
|
||||
async def get_event_types(_: Any = Depends(get_current_active_manage_user_async)) -> Any:
|
||||
"""
|
||||
获取所有事件类型
|
||||
"""
|
||||
@@ -98,7 +99,7 @@ async def get_event_types(_: User = Depends(get_current_active_manage_user_async
|
||||
|
||||
@router.post("/share", summary="分享工作流", response_model=_SchemaResponse[None])
|
||||
async def workflow_share(
|
||||
workflow: _SchemaWorkflowShare, _: User = Depends(get_current_active_manage_user_async)
|
||||
workflow: _SchemaWorkflowShare, _: Any = Depends(get_current_active_manage_user_async)
|
||||
) -> Any:
|
||||
"""
|
||||
分享工作流
|
||||
@@ -119,7 +120,7 @@ async def workflow_share(
|
||||
|
||||
@router.delete("/share/{share_id}", summary="删除分享", response_model=_SchemaResponse[None])
|
||||
async def workflow_share_delete(
|
||||
share_id: int, _: User = Depends(get_current_active_manage_user_async)
|
||||
share_id: int, _: Any = Depends(get_current_active_manage_user_async)
|
||||
) -> Any:
|
||||
"""
|
||||
删除分享
|
||||
@@ -132,7 +133,7 @@ async def workflow_share_delete(
|
||||
async def workflow_fork(
|
||||
workflow: _SchemaWorkflowShare,
|
||||
command: WorkflowDefinitionCommand = Depends(get_workflow_definition_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: Any = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
复用工作流
|
||||
@@ -148,7 +149,7 @@ async def workflow_shares(
|
||||
name: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
count: Optional[int] = 30,
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: Any = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
查询分享的工作流
|
||||
@@ -162,7 +163,7 @@ async def workflow_shares(
|
||||
def run_workflow(
|
||||
workflow_id: int,
|
||||
from_begin: Optional[bool] = True,
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: Any = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
执行工作流
|
||||
@@ -179,7 +180,7 @@ def run_workflow(
|
||||
def start_workflow(
|
||||
workflow_id: int,
|
||||
command: WorkflowMutationCommand = Depends(get_workflow_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: Any = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
启用工作流
|
||||
@@ -194,7 +195,7 @@ def start_workflow(
|
||||
def pause_workflow(
|
||||
workflow_id: int,
|
||||
command: WorkflowMutationCommand = Depends(get_workflow_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: Any = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
停用工作流
|
||||
@@ -209,7 +210,7 @@ def pause_workflow(
|
||||
async def reset_workflow(
|
||||
workflow_id: int,
|
||||
command: WorkflowDefinitionCommand = Depends(get_workflow_definition_command),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
_: Any = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
重置工作流
|
||||
@@ -221,20 +222,20 @@ async def reset_workflow(
|
||||
@router.get("/{workflow_id}", summary="工作流详情", response_model=_SchemaWorkflow)
|
||||
async def get_workflow(
|
||||
workflow_id: int,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
_: User = Depends(get_current_active_manage_user_async),
|
||||
query: WorkflowQueryService = Depends(get_workflow_query_service),
|
||||
_: Any = Depends(get_current_active_manage_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
获取工作流详情
|
||||
"""
|
||||
return await WorkflowOper(db).async_get(workflow_id)
|
||||
return await query.get(workflow_id)
|
||||
|
||||
|
||||
@router.put("/{workflow_id}", summary="更新工作流", response_model=_SchemaResponse[None])
|
||||
def update_workflow(
|
||||
workflow: _SchemaWorkflow,
|
||||
command: WorkflowMutationCommand = Depends(get_workflow_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: Any = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
更新工作流
|
||||
@@ -247,7 +248,7 @@ def update_workflow(
|
||||
def delete_workflow(
|
||||
workflow_id: int,
|
||||
command: WorkflowMutationCommand = Depends(get_workflow_mutation_command),
|
||||
_: User = Depends(get_current_active_manage_user),
|
||||
_: Any = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
删除工作流
|
||||
|
||||
Reference in New Issue
Block a user