mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
refactor: split api dependencies by domain
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""按业务领域拆分的 FastAPI 依赖工厂。"""
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Agent 与消息查询依赖。"""
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.context import get_agent_chat_repository, get_agent_chat_transaction
|
||||
from app.api.data import get_async_db
|
||||
from app.api.dependencies.data import repository
|
||||
from app.application.messaging.chat import (
|
||||
AgentChatService,
|
||||
AsyncAgentChatRepository,
|
||||
AsyncUnitOfWork,
|
||||
)
|
||||
from app.application.messaging.message import MessageQueryService
|
||||
|
||||
|
||||
def get_agent_chat_service(
|
||||
chat_repository: AsyncAgentChatRepository = Depends(get_agent_chat_repository),
|
||||
unit_of_work: AsyncUnitOfWork = Depends(get_agent_chat_transaction),
|
||||
) -> AgentChatService:
|
||||
"""组装类型化 Agent 会话历史查询和删除服务。"""
|
||||
return AgentChatService(chat_repository, unit_of_work)
|
||||
|
||||
|
||||
def get_message_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> MessageQueryService:
|
||||
"""组装消息历史异步查询服务。"""
|
||||
return MessageQueryService(repository=repository("message", db))
|
||||
@@ -0,0 +1,116 @@
|
||||
"""用户身份、授权与认证服务依赖。"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.data import get_async_db, get_db
|
||||
from app.api.dependencies.data import repository, standalone_repository
|
||||
from app.application.security.auth import AuthService
|
||||
from app.application.security.passkeys import PasskeyService
|
||||
from app.application.security.user import UserService
|
||||
from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
|
||||
|
||||
def get_user_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> UserService:
|
||||
"""组装用户管理应用服务。"""
|
||||
return UserService(repository=repository("user", db))
|
||||
|
||||
|
||||
def get_auth_service() -> AuthService:
|
||||
"""组装同步认证应用服务。"""
|
||||
return AuthService(
|
||||
users=standalone_repository("user"),
|
||||
config=standalone_repository("system_config"),
|
||||
passkeys=standalone_repository("passkey"),
|
||||
)
|
||||
|
||||
|
||||
def get_passkey_service() -> PasskeyService:
|
||||
"""组装 PassKey 应用服务。"""
|
||||
return PasskeyService(repository=standalone_repository("passkey"))
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db),
|
||||
token_data: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""读取令牌对应用户,不存在时返回 403。"""
|
||||
user = repository("user", db).get_by_id(token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=403, detail="用户不存在")
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_user_async(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
token_data: _SchemaTokenPayload = Depends(verify_token),
|
||||
) -> Any:
|
||||
"""异步读取令牌对应用户,不存在时返回 403。"""
|
||||
user = await repository("user", db).async_get_by_id(token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=403, detail="用户不存在")
|
||||
return user
|
||||
|
||||
|
||||
def get_current_active_user(
|
||||
current_user: Any = Depends(get_current_user),
|
||||
) -> Any:
|
||||
"""校验并返回当前激活用户。"""
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户未激活")
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_current_active_user_async(
|
||||
current_user: Any = Depends(get_current_user_async),
|
||||
) -> Any:
|
||||
"""异步校验并返回当前激活用户。"""
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户未激活")
|
||||
return current_user
|
||||
|
||||
|
||||
def _ensure_manage_user(current_user: Any) -> Any:
|
||||
"""校验用户具备全局管理权限。"""
|
||||
permissions = current_user.permissions or {}
|
||||
if not current_user.is_superuser and not bool(permissions.get("manage")):
|
||||
raise HTTPException(status_code=400, detail="用户权限不足")
|
||||
return current_user
|
||||
|
||||
|
||||
def get_current_active_manage_user(
|
||||
current_user: Any = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""返回当前拥有管理权限的激活用户。"""
|
||||
return _ensure_manage_user(current_user)
|
||||
|
||||
|
||||
async def get_current_active_manage_user_async(
|
||||
current_user: Any = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""异步返回当前拥有管理权限的激活用户。"""
|
||||
return _ensure_manage_user(current_user)
|
||||
|
||||
|
||||
def get_current_active_superuser(
|
||||
current_user: Any = Depends(get_current_user),
|
||||
) -> Any:
|
||||
"""校验并返回当前激活超级管理员。"""
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(status_code=400, detail="用户权限不足")
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_current_active_superuser_async(
|
||||
current_user: Any = Depends(get_current_user_async),
|
||||
) -> Any:
|
||||
"""异步校验并返回当前激活超级管理员。"""
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(status_code=400, detail="用户权限不足")
|
||||
return current_user
|
||||
@@ -0,0 +1,20 @@
|
||||
"""未迁移领域共用的 API 数据兼容 Facade。"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.api.data import get_api_data_ports
|
||||
|
||||
|
||||
def repository(name: str, session: Any) -> Any:
|
||||
"""按旧能力名构造绑定当前请求会话的仓储。"""
|
||||
return get_api_data_ports().repository(name, session)
|
||||
|
||||
|
||||
def standalone_repository(name: str) -> Any:
|
||||
"""按旧能力名构造无需绑定请求会话的仓储。"""
|
||||
return get_api_data_ports().standalone_repository(name)
|
||||
|
||||
|
||||
def transaction(name: str, session: Any) -> Any:
|
||||
"""按旧能力名构造绑定当前请求会话的事务端口。"""
|
||||
return get_api_data_ports().transaction(name, session)
|
||||
@@ -0,0 +1,86 @@
|
||||
"""历史、媒体服务器与 Dashboard 查询依赖。"""
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.data import get_async_db, get_db
|
||||
from app.api.dependencies.data import repository, transaction
|
||||
from app.application.dashboard import DashboardQueryService
|
||||
from app.application.history import (
|
||||
DownloadHistoryMutationCommand,
|
||||
HistoryQueryService,
|
||||
TransferHistoryLookupService,
|
||||
TransferHistoryMutationCommand,
|
||||
clear_transfer_failures,
|
||||
)
|
||||
from app.application.mediaserver import MediaServerQueryService
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.events import eventmanager
|
||||
from app.schemas.types import EventType
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
|
||||
|
||||
def get_mediaserver_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> MediaServerQueryService:
|
||||
"""组装媒体服务器本地条目异步查询服务。"""
|
||||
return MediaServerQueryService(repository=repository("media_server", db))
|
||||
|
||||
|
||||
def get_dashboard_query_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> DashboardQueryService:
|
||||
"""组装 Dashboard 媒体与整理历史统计查询服务。"""
|
||||
from app.chain.dashboard import DashboardChain
|
||||
|
||||
return DashboardQueryService(
|
||||
repository=repository("transfer_history", db),
|
||||
media_statistics=DashboardChain().media_statistic,
|
||||
)
|
||||
|
||||
|
||||
def get_download_history_mutation_command(
|
||||
db: Session = Depends(get_db),
|
||||
) -> DownloadHistoryMutationCommand:
|
||||
"""组装下载历史删除用例及其请求级事务。"""
|
||||
return DownloadHistoryMutationCommand(
|
||||
repository=repository("download_history", db),
|
||||
unit_of_work=transaction("sync", db),
|
||||
)
|
||||
|
||||
|
||||
def get_history_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> HistoryQueryService:
|
||||
"""组装历史列表和详情异步查询服务。"""
|
||||
return HistoryQueryService(
|
||||
download_repository=repository("download_history", db),
|
||||
transfer_repository=repository("transfer_history", db),
|
||||
)
|
||||
|
||||
|
||||
def get_transfer_history_lookup_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> TransferHistoryLookupService:
|
||||
"""组装手动整理使用的同步历史投影服务。"""
|
||||
return TransferHistoryLookupService(repository("transfer_history", db))
|
||||
|
||||
|
||||
def get_transfer_history_mutation_command(
|
||||
db: Session = Depends(get_db),
|
||||
) -> TransferHistoryMutationCommand:
|
||||
"""组装整理历史删除、文件处理和事件发布用例。"""
|
||||
storage_chain = StorageChain()
|
||||
return TransferHistoryMutationCommand(
|
||||
repository=repository("transfer_history", db),
|
||||
download_repository=repository("download_history", db),
|
||||
unit_of_work=transaction("sync", db),
|
||||
file_item_factory=lambda payload: _SchemaFileItem(**payload),
|
||||
delete_media_file=storage_chain.delete_media_file,
|
||||
publish_download_file_deleted=lambda payload: eventmanager.send_event(
|
||||
EventType.DownloadFileDeleted,
|
||||
payload,
|
||||
),
|
||||
clear_failures=clear_transfer_failures,
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
"""插件配置与运行态刷新依赖。"""
|
||||
|
||||
from app.application.commands import init_commands
|
||||
from app.application.plugin.config import PluginConfigCommand
|
||||
from app.application.plugin.routes import register_plugin_api
|
||||
from app.application.plugin.runtime import get_plugin_manager
|
||||
from app.application.scheduling import update_plugin_job
|
||||
from app.runtime.events import eventmanager
|
||||
from app.schemas.event import PluginDataResetEventData
|
||||
from app.schemas.types import ChainEventType
|
||||
|
||||
|
||||
def get_plugin_config_command() -> PluginConfigCommand:
|
||||
"""组装插件配置更新与重置用例,隔离 API 对运行时写操作的编排。"""
|
||||
manager = get_plugin_manager()
|
||||
|
||||
def publish_reset(plugin_id: str) -> None:
|
||||
"""在清理持久化数据前通知目标插件执行补偿。"""
|
||||
eventmanager.send_event(
|
||||
ChainEventType.PluginDataReset,
|
||||
PluginDataResetEventData(
|
||||
plugin_id=plugin_id,
|
||||
reset_config=True,
|
||||
reset_data=True,
|
||||
),
|
||||
)
|
||||
|
||||
def refresh_registrations(plugin_id: str) -> None:
|
||||
"""按服务、命令、动态路由顺序刷新插件宿主注册。"""
|
||||
update_plugin_job(plugin_id)
|
||||
init_commands(plugin_id)
|
||||
register_plugin_api(plugin_id)
|
||||
|
||||
return PluginConfigCommand(
|
||||
save_config=manager.save_plugin_config,
|
||||
initialize=manager.init_plugin,
|
||||
stop=manager.stop,
|
||||
delete_config=manager.delete_plugin_config,
|
||||
delete_data=manager.delete_plugin_data,
|
||||
reload_runtime=manager.reload_plugin,
|
||||
publish_reset=publish_reset,
|
||||
refresh_registrations=refresh_registrations,
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
"""站点领域的请求级 command/query 依赖。"""
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.data import get_async_db, get_db
|
||||
from app.api.dependencies.data import repository, transaction
|
||||
from app.application.site.mutation import SiteMutationCommand
|
||||
from app.application.site.query import SiteQueryService
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from app.domain import site as site_rules
|
||||
from app.foundation import url as url_tools
|
||||
from app.runtime.events import eventmanager
|
||||
from app.schemas.types import EventType
|
||||
|
||||
|
||||
async def _publish_site_updated(payload: dict) -> None:
|
||||
"""发布已提交的站点更新事件。"""
|
||||
await eventmanager.async_send_event(EventType.SiteUpdated, payload)
|
||||
|
||||
|
||||
async def _publish_site_deleted(payload: dict) -> None:
|
||||
"""发布已提交的站点删除事件。"""
|
||||
await eventmanager.async_send_event(EventType.SiteDeleted, payload)
|
||||
|
||||
|
||||
def get_site_mutation_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SiteMutationCommand:
|
||||
"""组装请求级站点写用例及其事务和外部目录依赖。"""
|
||||
sites_helper = SitesHelper()
|
||||
|
||||
def normalize_url(value: str) -> str:
|
||||
"""沿用站点接口的 scheme/netloc 规范化格式。"""
|
||||
scheme, netloc = url_tools.split_netloc(value)
|
||||
return f"{scheme}://{netloc}/"
|
||||
|
||||
return SiteMutationCommand(
|
||||
repository=repository("site", db),
|
||||
unit_of_work=transaction("async", db),
|
||||
auth_level_provider=lambda: sites_helper.auth_level,
|
||||
indexer_loader=sites_helper.async_get_indexer,
|
||||
domain_extractor=site_rules.extract_domain,
|
||||
url_normalizer=normalize_url,
|
||||
publish_updated=_publish_site_updated,
|
||||
publish_deleted=_publish_site_deleted,
|
||||
)
|
||||
|
||||
|
||||
def get_site_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SiteQueryService:
|
||||
"""组装站点异步查询服务。"""
|
||||
return SiteQueryService(repository=repository("site", db))
|
||||
|
||||
|
||||
def get_site_sync_query_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> SiteQueryService:
|
||||
"""组装站点同步查询服务,用于同步 Chain 路由。"""
|
||||
return SiteQueryService(repository=repository("site", db))
|
||||
@@ -0,0 +1,125 @@
|
||||
"""订阅领域的请求级 command/query 依赖。"""
|
||||
|
||||
from fastapi import BackgroundTasks, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.api.data import get_async_db, get_db
|
||||
from app.api.dependencies.data import repository, transaction
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.application.servarr import ServarrSubscriptionService
|
||||
from app.application.subscription.delete import DeleteSubscribeCommand
|
||||
from app.application.subscription.identity import DeleteSubscriptionsByIdentityCommand
|
||||
from app.application.subscription.mutation import SubscriptionMutationService
|
||||
from app.application.subscription.query import SubscriptionQueryService
|
||||
from app.application.subscription.search import SearchSubscriptionsCommand
|
||||
from app.runtime.events import eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import EventType
|
||||
|
||||
|
||||
async def _publish_subscribe_deleted(
|
||||
subscribe_id: int,
|
||||
subscribe_info: dict,
|
||||
) -> None:
|
||||
"""通过宿主事件总线发布已提交的订阅删除事件。"""
|
||||
await eventmanager.async_send_event(
|
||||
EventType.SubscribeDeleted,
|
||||
{"subscribe_id": subscribe_id, "subscribe_info": subscribe_info},
|
||||
)
|
||||
|
||||
|
||||
def get_delete_subscribe_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> DeleteSubscribeCommand:
|
||||
"""组装请求级订阅删除用例及其具体适配器。"""
|
||||
return DeleteSubscribeCommand(
|
||||
repository=repository("subscribe", db),
|
||||
unit_of_work=transaction("async", db),
|
||||
publish_deleted=_publish_subscribe_deleted,
|
||||
report_deleted=MoviePilotServerHelper.sub_done_async,
|
||||
)
|
||||
|
||||
|
||||
def _log_subscribe_deleted_event_error(
|
||||
subscribe_id: int,
|
||||
error: Exception,
|
||||
) -> None:
|
||||
"""记录按媒体身份删除时的单条事件失败并允许后续事件继续。"""
|
||||
logger.error(
|
||||
f"发送订阅删除事件失败:{subscribe_id} - {error}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
|
||||
def get_delete_subscriptions_by_identity_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> DeleteSubscriptionsByIdentityCommand:
|
||||
"""组装请求级按媒体身份删除订阅用例。"""
|
||||
return DeleteSubscriptionsByIdentityCommand(
|
||||
repository=repository("subscribe", db),
|
||||
unit_of_work=transaction("async", db),
|
||||
publish_deleted=_publish_subscribe_deleted,
|
||||
handle_event_error=_log_subscribe_deleted_event_error,
|
||||
)
|
||||
|
||||
|
||||
def get_search_subscriptions_command(
|
||||
background_tasks: BackgroundTasks,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SearchSubscriptionsCommand:
|
||||
"""组装手工订阅搜索用例,并把调度延迟到响应后的后台任务。"""
|
||||
def schedule_search(subscribe_id: int | None, state: str | None) -> None:
|
||||
"""按历史参数提交订阅搜索调度任务。"""
|
||||
background_tasks.add_task(
|
||||
Scheduler().start,
|
||||
job_id="subscribe_search",
|
||||
sid=subscribe_id,
|
||||
state=state,
|
||||
manual=True,
|
||||
)
|
||||
|
||||
return SearchSubscriptionsCommand(
|
||||
repository=repository("subscribe", db),
|
||||
schedule_search=schedule_search,
|
||||
)
|
||||
|
||||
|
||||
def get_subscription_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SubscriptionQueryService:
|
||||
"""组装订阅和订阅历史异步查询服务。"""
|
||||
return SubscriptionQueryService(
|
||||
repository=repository("subscribe", db),
|
||||
async_repository=repository("subscribe", db),
|
||||
history_repository=repository("subscribe_history", db),
|
||||
)
|
||||
|
||||
|
||||
def get_subscription_mutation_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SubscriptionMutationService:
|
||||
"""组装异步订阅写服务。"""
|
||||
return SubscriptionMutationService(
|
||||
repository=repository("subscribe", db),
|
||||
history_repository=repository("subscribe_history", db),
|
||||
)
|
||||
|
||||
|
||||
def get_subscription_sync_mutation_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> SubscriptionMutationService:
|
||||
"""组装同步订阅查询服务,供文件信息接口使用。"""
|
||||
return SubscriptionMutationService(repository=repository("subscribe", db))
|
||||
|
||||
|
||||
def get_servarr_subscription_service(
|
||||
async_db: AsyncSession = Depends(get_async_db),
|
||||
db: Session = Depends(get_db),
|
||||
) -> ServarrSubscriptionService:
|
||||
"""组装 Servarr 兼容路由的请求级订阅数据用例。"""
|
||||
return ServarrSubscriptionService(
|
||||
async_repository=repository("subscribe", async_db),
|
||||
sync_repository=repository("subscribe", db),
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
"""工作流领域的请求级 command/query 依赖。"""
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.api.data import get_async_db, get_db
|
||||
from app.api.dependencies.data import repository, standalone_repository, transaction
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.application.workflow import (
|
||||
WorkflowDefinitionCommand,
|
||||
WorkflowMutationCommand,
|
||||
WorkflowQueryService,
|
||||
)
|
||||
from app.runtime.config import global_vars
|
||||
from app.workflow import WorkFlowManager
|
||||
|
||||
|
||||
def get_workflow_mutation_command(
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkflowMutationCommand:
|
||||
"""组装请求级工作流写用例和提交后的调度副作用。"""
|
||||
scheduler = Scheduler()
|
||||
workflow_manager = WorkFlowManager()
|
||||
return WorkflowMutationCommand(
|
||||
repository=repository("workflow", db),
|
||||
unit_of_work=transaction("sync", db),
|
||||
add_timer=scheduler.update_workflow_job,
|
||||
remove_timer=scheduler.remove_workflow_job,
|
||||
load_event=workflow_manager.load_workflow_events,
|
||||
remove_event=workflow_manager.remove_workflow_event,
|
||||
refresh_event=workflow_manager.update_workflow_event,
|
||||
stop_running=global_vars.stop_workflow,
|
||||
delete_cache=lambda workflow_id: standalone_repository(
|
||||
"system_config"
|
||||
).delete(f"WorkflowCache-{workflow_id}"),
|
||||
)
|
||||
|
||||
|
||||
def get_workflow_definition_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> WorkflowDefinitionCommand:
|
||||
"""组装工作流创建、复用和重置的异步写用例。"""
|
||||
return WorkflowDefinitionCommand(
|
||||
repository=repository("workflow", db),
|
||||
unit_of_work=transaction("async", db),
|
||||
stop_running=global_vars.stop_workflow,
|
||||
delete_cache=lambda workflow_id: standalone_repository(
|
||||
"system_config"
|
||||
).delete(f"WorkflowCache-{workflow_id}"),
|
||||
report_fork=MoviePilotServerHelper.async_workflow_fork_by_id,
|
||||
)
|
||||
|
||||
|
||||
def get_workflow_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> WorkflowQueryService:
|
||||
"""组装工作流只读查询用例,避免端点直接持有数据库操作器。"""
|
||||
return WorkflowQueryService(repository=repository("workflow", db))
|
||||
+81
-517
@@ -1,524 +1,88 @@
|
||||
"""FastAPI 依赖兼容聚合入口。
|
||||
|
||||
新代码按领域从 ``app.api.dependencies`` 导入;本模块保留全部历史名字,避免端点、测试和
|
||||
旧 SDK 在依赖拆分期间同步改动导入路径。
|
||||
"""
|
||||
API 层的公共依赖。
|
||||
|
||||
这些是 FastAPI 的路由依赖:从令牌解出用户、校验激活状态与权限,失败一律以
|
||||
HTTPException 表达。它们此前住在 app/db/oper/user.py 里,与数据访问混在一处——
|
||||
鉴权是 HTTP 层的关注点,产出的是 403/400 而不是数据。放在 db 包里既让数据层反向
|
||||
依赖了 fastapi,也使这部分逻辑无法与数据访问分开度量。
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from fastapi import BackgroundTasks, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.schemas.token import TokenPayload as _SchemaTokenPayload
|
||||
from app.application.subscription.delete import DeleteSubscribeCommand
|
||||
from app.application.subscription.identity import (
|
||||
DeleteSubscriptionsByIdentityCommand,
|
||||
from app.api.dependencies.agent import (
|
||||
get_agent_chat_service,
|
||||
get_message_query_service,
|
||||
)
|
||||
from app.application.subscription.search import SearchSubscriptionsCommand
|
||||
from app.application.subscription.query import SubscriptionQueryService
|
||||
from app.application.subscription.mutation import SubscriptionMutationService
|
||||
from app.application.site.mutation import SiteMutationCommand
|
||||
from app.application.site.query import SiteQueryService
|
||||
from app.application.workflow import (
|
||||
WorkflowDefinitionCommand,
|
||||
WorkflowMutationCommand,
|
||||
WorkflowQueryService,
|
||||
from app.api.dependencies.auth import (
|
||||
get_auth_service,
|
||||
get_current_active_manage_user,
|
||||
get_current_active_manage_user_async,
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
get_current_active_user,
|
||||
get_current_active_user_async,
|
||||
get_current_user,
|
||||
get_current_user_async,
|
||||
get_passkey_service,
|
||||
get_user_service,
|
||||
)
|
||||
from app.application.messaging.message import MessageQueryService
|
||||
from app.application.messaging.chat import AgentChatService
|
||||
from app.api.context import (
|
||||
get_agent_chat_repository,
|
||||
get_agent_chat_transaction,
|
||||
from app.api.dependencies.history import (
|
||||
get_dashboard_query_service,
|
||||
get_download_history_mutation_command,
|
||||
get_history_query_service,
|
||||
get_mediaserver_query_service,
|
||||
get_transfer_history_lookup_service,
|
||||
get_transfer_history_mutation_command,
|
||||
)
|
||||
from app.application.messaging.chat import (
|
||||
AsyncAgentChatRepository,
|
||||
AsyncUnitOfWork as AgentChatUnitOfWork,
|
||||
from app.api.dependencies.plugin import get_plugin_config_command
|
||||
from app.api.dependencies.site import (
|
||||
get_site_mutation_command,
|
||||
get_site_query_service,
|
||||
get_site_sync_query_service,
|
||||
)
|
||||
from app.application.mediaserver import MediaServerQueryService
|
||||
from app.application.servarr import ServarrSubscriptionService
|
||||
from app.application.dashboard import DashboardQueryService
|
||||
from app.application.history import (
|
||||
DownloadHistoryMutationCommand,
|
||||
HistoryQueryService,
|
||||
TransferHistoryLookupService,
|
||||
TransferHistoryMutationCommand,
|
||||
clear_transfer_failures,
|
||||
from app.api.dependencies.subscription import (
|
||||
get_delete_subscribe_command,
|
||||
get_delete_subscriptions_by_identity_command,
|
||||
get_search_subscriptions_command,
|
||||
get_servarr_subscription_service,
|
||||
get_subscription_mutation_service,
|
||||
get_subscription_query_service,
|
||||
get_subscription_sync_mutation_service,
|
||||
)
|
||||
from app.application.plugin.config import PluginConfigCommand
|
||||
from app.application.commands import init_commands
|
||||
from app.application.plugin.routes import register_plugin_api
|
||||
from app.application.scheduling import update_plugin_job
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.application.security.user import UserService
|
||||
from app.application.security.auth import AuthService
|
||||
from app.application.security.passkeys import PasskeyService
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.api.data import get_api_data_ports, get_async_db, get_db
|
||||
from app.runtime.events import eventmanager
|
||||
from app.application.plugin.runtime import get_plugin_manager as PluginManager
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.event import PluginDataResetEventData
|
||||
from app.schemas.types import ChainEventType, EventType
|
||||
from app.application.scheduling import Scheduler
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from app.domain import site as site_rules
|
||||
from app.foundation import url as url_tools
|
||||
from app.runtime.config import global_vars
|
||||
from app.workflow import WorkFlowManager
|
||||
from app.chain.storage import StorageChain
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
|
||||
|
||||
def _repository(name: str, session: Any) -> Any:
|
||||
"""构造绑定当前请求会话的数据仓储。"""
|
||||
return get_api_data_ports().repository(name, session)
|
||||
|
||||
|
||||
def _standalone_repository(name: str) -> Any:
|
||||
"""构造无需绑定请求会话的数据端口。"""
|
||||
return get_api_data_ports().standalone_repository(name)
|
||||
|
||||
|
||||
def _transaction(name: str, session: Any) -> Any:
|
||||
"""构造绑定当前请求会话的事务端口。"""
|
||||
return get_api_data_ports().transaction(name, session)
|
||||
|
||||
|
||||
async def _publish_subscribe_deleted(
|
||||
subscribe_id: int,
|
||||
subscribe_info: dict,
|
||||
) -> None:
|
||||
"""通过宿主事件总线发布已提交的订阅删除事件。"""
|
||||
await eventmanager.async_send_event(
|
||||
EventType.SubscribeDeleted,
|
||||
{"subscribe_id": subscribe_id, "subscribe_info": subscribe_info},
|
||||
)
|
||||
|
||||
|
||||
def get_delete_subscribe_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> DeleteSubscribeCommand:
|
||||
"""组装请求级订阅删除用例及其具体适配器。"""
|
||||
return DeleteSubscribeCommand(
|
||||
repository=_repository("subscribe", db),
|
||||
unit_of_work=_transaction("async", db),
|
||||
publish_deleted=_publish_subscribe_deleted,
|
||||
report_deleted=MoviePilotServerHelper.sub_done_async,
|
||||
)
|
||||
|
||||
|
||||
def _log_subscribe_deleted_event_error(
|
||||
subscribe_id: int,
|
||||
error: Exception,
|
||||
) -> None:
|
||||
"""记录按媒体身份删除时的单条事件失败并允许后续事件继续。"""
|
||||
logger.error(
|
||||
f"发送订阅删除事件失败:{subscribe_id} - {error}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
|
||||
def get_delete_subscriptions_by_identity_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> DeleteSubscriptionsByIdentityCommand:
|
||||
"""组装请求级按媒体身份删除订阅用例。"""
|
||||
return DeleteSubscriptionsByIdentityCommand(
|
||||
repository=_repository("subscribe", db),
|
||||
unit_of_work=_transaction("async", db),
|
||||
publish_deleted=_publish_subscribe_deleted,
|
||||
handle_event_error=_log_subscribe_deleted_event_error,
|
||||
)
|
||||
|
||||
|
||||
def get_search_subscriptions_command(
|
||||
background_tasks: BackgroundTasks,
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SearchSubscriptionsCommand:
|
||||
"""组装手工订阅搜索用例,并把调度延迟到响应后的后台任务。"""
|
||||
def schedule_search(subscribe_id: int | None, state: str | None) -> None:
|
||||
"""按历史参数提交订阅搜索调度任务。"""
|
||||
background_tasks.add_task(
|
||||
Scheduler().start,
|
||||
job_id="subscribe_search",
|
||||
sid=subscribe_id,
|
||||
state=state,
|
||||
manual=True,
|
||||
)
|
||||
|
||||
return SearchSubscriptionsCommand(
|
||||
repository=_repository("subscribe", db),
|
||||
schedule_search=schedule_search,
|
||||
)
|
||||
|
||||
|
||||
def get_subscription_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SubscriptionQueryService:
|
||||
"""组装订阅和订阅历史异步查询服务。"""
|
||||
return SubscriptionQueryService(
|
||||
repository=_repository("subscribe", db),
|
||||
async_repository=_repository("subscribe", db),
|
||||
history_repository=_repository("subscribe_history", db),
|
||||
)
|
||||
|
||||
|
||||
def get_user_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> UserService:
|
||||
"""组装用户管理应用服务。"""
|
||||
return UserService(repository=_repository("user", db))
|
||||
|
||||
|
||||
def get_auth_service() -> AuthService:
|
||||
"""组装同步认证应用服务。"""
|
||||
return AuthService(
|
||||
users=_standalone_repository("user"),
|
||||
config=_standalone_repository("system_config"),
|
||||
passkeys=_standalone_repository("passkey"),
|
||||
)
|
||||
|
||||
|
||||
def get_passkey_service() -> PasskeyService:
|
||||
"""组装 PassKey 应用服务。"""
|
||||
return PasskeyService(repository=_standalone_repository("passkey"))
|
||||
|
||||
|
||||
def get_subscription_mutation_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SubscriptionMutationService:
|
||||
"""组装异步订阅写服务。"""
|
||||
return SubscriptionMutationService(
|
||||
repository=_repository("subscribe", db),
|
||||
history_repository=_repository("subscribe_history", db),
|
||||
)
|
||||
|
||||
|
||||
def get_subscription_sync_mutation_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> SubscriptionMutationService:
|
||||
"""组装同步订阅查询服务,供文件信息接口使用。"""
|
||||
return SubscriptionMutationService(repository=_repository("subscribe", db))
|
||||
|
||||
|
||||
def get_servarr_subscription_service(
|
||||
async_db: AsyncSession = Depends(get_async_db),
|
||||
db: Session = Depends(get_db),
|
||||
) -> ServarrSubscriptionService:
|
||||
"""组装 Servarr 兼容路由的请求级订阅数据用例。"""
|
||||
return ServarrSubscriptionService(
|
||||
async_repository=_repository("subscribe", async_db),
|
||||
sync_repository=_repository("subscribe", db),
|
||||
)
|
||||
|
||||
|
||||
async def _publish_site_updated(payload: dict) -> None:
|
||||
"""发布已提交的站点更新事件。"""
|
||||
await eventmanager.async_send_event(EventType.SiteUpdated, payload)
|
||||
|
||||
|
||||
async def _publish_site_deleted(payload: dict) -> None:
|
||||
"""发布已提交的站点删除事件。"""
|
||||
await eventmanager.async_send_event(EventType.SiteDeleted, payload)
|
||||
|
||||
|
||||
def get_site_mutation_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SiteMutationCommand:
|
||||
"""组装请求级站点写用例及其事务和外部目录依赖。"""
|
||||
sites_helper = SitesHelper()
|
||||
|
||||
def normalize_url(value: str) -> str:
|
||||
"""沿用站点接口的 scheme/netloc 规范化格式。"""
|
||||
scheme, netloc = url_tools.split_netloc(value)
|
||||
return f"{scheme}://{netloc}/"
|
||||
|
||||
return SiteMutationCommand(
|
||||
repository=_repository("site", db),
|
||||
unit_of_work=_transaction("async", db),
|
||||
auth_level_provider=lambda: sites_helper.auth_level,
|
||||
indexer_loader=sites_helper.async_get_indexer,
|
||||
domain_extractor=site_rules.extract_domain,
|
||||
url_normalizer=normalize_url,
|
||||
publish_updated=_publish_site_updated,
|
||||
publish_deleted=_publish_site_deleted,
|
||||
)
|
||||
|
||||
|
||||
def get_site_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> SiteQueryService:
|
||||
"""组装站点异步查询服务。"""
|
||||
return SiteQueryService(repository=_repository("site", db))
|
||||
|
||||
|
||||
def get_site_sync_query_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> SiteQueryService:
|
||||
"""组装站点同步查询服务,用于同步 Chain 路由。"""
|
||||
return SiteQueryService(repository=_repository("site", db))
|
||||
|
||||
|
||||
def get_workflow_mutation_command(
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkflowMutationCommand:
|
||||
"""组装请求级工作流写用例和提交后的调度副作用。"""
|
||||
scheduler = Scheduler()
|
||||
workflow_manager = WorkFlowManager()
|
||||
return WorkflowMutationCommand(
|
||||
repository=_repository("workflow", db),
|
||||
unit_of_work=_transaction("sync", db),
|
||||
add_timer=scheduler.update_workflow_job,
|
||||
remove_timer=scheduler.remove_workflow_job,
|
||||
load_event=workflow_manager.load_workflow_events,
|
||||
remove_event=workflow_manager.remove_workflow_event,
|
||||
refresh_event=workflow_manager.update_workflow_event,
|
||||
stop_running=global_vars.stop_workflow,
|
||||
delete_cache=lambda workflow_id: _standalone_repository("system_config").delete(
|
||||
f"WorkflowCache-{workflow_id}"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def get_workflow_definition_command(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> WorkflowDefinitionCommand:
|
||||
"""组装工作流创建、复用和重置的异步写用例。"""
|
||||
return WorkflowDefinitionCommand(
|
||||
repository=_repository("workflow", db),
|
||||
unit_of_work=_transaction("async", db),
|
||||
stop_running=global_vars.stop_workflow,
|
||||
delete_cache=lambda workflow_id: _standalone_repository("system_config").delete(
|
||||
f"WorkflowCache-{workflow_id}"
|
||||
),
|
||||
report_fork=MoviePilotServerHelper.async_workflow_fork_by_id,
|
||||
)
|
||||
|
||||
|
||||
def get_workflow_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> WorkflowQueryService:
|
||||
"""组装工作流只读查询用例,避免端点直接持有数据库操作器。"""
|
||||
return WorkflowQueryService(repository=_repository("workflow", db))
|
||||
|
||||
|
||||
def get_message_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> MessageQueryService:
|
||||
"""组装消息历史异步查询服务。"""
|
||||
return MessageQueryService(repository=_repository("message", db))
|
||||
|
||||
|
||||
def get_agent_chat_service(
|
||||
repository: AsyncAgentChatRepository = Depends(get_agent_chat_repository),
|
||||
unit_of_work: AgentChatUnitOfWork = Depends(get_agent_chat_transaction),
|
||||
) -> AgentChatService:
|
||||
"""组装 Agent 会话历史查询和删除服务。"""
|
||||
return AgentChatService(
|
||||
repository=repository,
|
||||
unit_of_work=unit_of_work,
|
||||
)
|
||||
|
||||
|
||||
def get_mediaserver_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> MediaServerQueryService:
|
||||
"""组装媒体服务器本地条目异步查询服务。"""
|
||||
return MediaServerQueryService(repository=_repository("media_server", db))
|
||||
|
||||
|
||||
def get_dashboard_query_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> DashboardQueryService:
|
||||
"""组装 Dashboard 媒体与整理历史统计查询服务。"""
|
||||
from app.chain.dashboard import DashboardChain
|
||||
|
||||
return DashboardQueryService(
|
||||
repository=_repository("transfer_history", db),
|
||||
media_statistics=DashboardChain().media_statistic,
|
||||
)
|
||||
|
||||
|
||||
def get_download_history_mutation_command(
|
||||
db: Session = Depends(get_db),
|
||||
) -> DownloadHistoryMutationCommand:
|
||||
"""组装下载历史删除用例及其请求级事务。"""
|
||||
return DownloadHistoryMutationCommand(
|
||||
repository=_repository("download_history", db),
|
||||
unit_of_work=_transaction("sync", db),
|
||||
)
|
||||
|
||||
|
||||
def get_history_query_service(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
) -> HistoryQueryService:
|
||||
"""组装历史列表和详情异步查询服务。"""
|
||||
return HistoryQueryService(
|
||||
download_repository=_repository("download_history", db),
|
||||
transfer_repository=_repository("transfer_history", db),
|
||||
)
|
||||
|
||||
|
||||
def get_transfer_history_lookup_service(
|
||||
db: Session = Depends(get_db),
|
||||
) -> TransferHistoryLookupService:
|
||||
"""组装手动整理使用的同步历史投影服务。"""
|
||||
return TransferHistoryLookupService(_repository("transfer_history", db))
|
||||
|
||||
|
||||
def get_transfer_history_mutation_command(
|
||||
db: Session = Depends(get_db),
|
||||
) -> TransferHistoryMutationCommand:
|
||||
"""组装整理历史删除、文件处理和事件发布用例。"""
|
||||
storage_chain = StorageChain()
|
||||
return TransferHistoryMutationCommand(
|
||||
repository=_repository("transfer_history", db),
|
||||
download_repository=_repository("download_history", db),
|
||||
unit_of_work=_transaction("sync", db),
|
||||
file_item_factory=lambda payload: _SchemaFileItem(**payload),
|
||||
delete_media_file=storage_chain.delete_media_file,
|
||||
publish_download_file_deleted=lambda payload: eventmanager.send_event(
|
||||
EventType.DownloadFileDeleted,
|
||||
payload,
|
||||
),
|
||||
clear_failures=clear_transfer_failures,
|
||||
)
|
||||
|
||||
|
||||
def get_plugin_config_command() -> PluginConfigCommand:
|
||||
"""组装插件配置更新与重置用例,隔离 API 对运行时写操作的编排。"""
|
||||
manager = PluginManager()
|
||||
|
||||
def publish_reset(plugin_id: str) -> None:
|
||||
"""在清理持久化数据前通知目标插件执行补偿。"""
|
||||
eventmanager.send_event(
|
||||
ChainEventType.PluginDataReset,
|
||||
PluginDataResetEventData(
|
||||
plugin_id=plugin_id,
|
||||
reset_config=True,
|
||||
reset_data=True,
|
||||
),
|
||||
)
|
||||
|
||||
def refresh_registrations(plugin_id: str) -> None:
|
||||
"""按服务、命令、动态路由顺序刷新插件宿主注册。"""
|
||||
update_plugin_job(plugin_id)
|
||||
init_commands(plugin_id)
|
||||
register_plugin_api(plugin_id)
|
||||
|
||||
return PluginConfigCommand(
|
||||
save_config=manager.save_plugin_config,
|
||||
initialize=manager.init_plugin,
|
||||
stop=manager.stop,
|
||||
delete_config=manager.delete_plugin_config,
|
||||
delete_data=manager.delete_plugin_data,
|
||||
reload_runtime=manager.reload_plugin,
|
||||
publish_reset=publish_reset,
|
||||
refresh_registrations=refresh_registrations,
|
||||
)
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db),
|
||||
token_data: _SchemaTokenPayload = Depends(verify_token)
|
||||
) -> Any:
|
||||
"""
|
||||
获取当前用户
|
||||
"""
|
||||
user = _repository("user", db).get_by_id(token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=403, detail="用户不存在")
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_user_async(
|
||||
db: AsyncSession = Depends(get_async_db),
|
||||
token_data: _SchemaTokenPayload = Depends(verify_token)
|
||||
) -> Any:
|
||||
"""
|
||||
异步获取当前用户
|
||||
"""
|
||||
user = await _repository("user", db).async_get_by_id(token_data.sub)
|
||||
if not user:
|
||||
raise HTTPException(status_code=403, detail="用户不存在")
|
||||
return user
|
||||
|
||||
|
||||
def get_current_active_user(
|
||||
current_user: Any = Depends(get_current_user),
|
||||
) -> Any:
|
||||
"""
|
||||
获取当前激活用户
|
||||
"""
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户未激活")
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_current_active_user_async(
|
||||
current_user: Any = Depends(get_current_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
异步获取当前激活用户
|
||||
"""
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=403, detail="用户未激活")
|
||||
return current_user
|
||||
|
||||
|
||||
def _ensure_manage_user(current_user: Any) -> Any:
|
||||
"""
|
||||
校验用户具备全局管理权限。
|
||||
"""
|
||||
permissions = current_user.permissions or {}
|
||||
if not current_user.is_superuser and not bool(permissions.get("manage")):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="用户权限不足"
|
||||
)
|
||||
return current_user
|
||||
|
||||
|
||||
def get_current_active_manage_user(
|
||||
current_user: Any = Depends(get_current_active_user),
|
||||
) -> Any:
|
||||
"""
|
||||
获取当前拥有管理权限的激活用户。
|
||||
"""
|
||||
return _ensure_manage_user(current_user)
|
||||
|
||||
|
||||
async def get_current_active_manage_user_async(
|
||||
current_user: Any = Depends(get_current_active_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
异步获取当前拥有管理权限的激活用户。
|
||||
"""
|
||||
return _ensure_manage_user(current_user)
|
||||
|
||||
|
||||
def get_current_active_superuser(
|
||||
current_user: Any = Depends(get_current_user),
|
||||
) -> Any:
|
||||
"""
|
||||
获取当前激活超级管理员
|
||||
"""
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="用户权限不足"
|
||||
)
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_current_active_superuser_async(
|
||||
current_user: Any = Depends(get_current_user_async),
|
||||
) -> Any:
|
||||
"""
|
||||
异步获取当前激活超级管理员
|
||||
"""
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="用户权限不足"
|
||||
)
|
||||
return current_user
|
||||
from app.api.dependencies.workflow import (
|
||||
get_workflow_definition_command,
|
||||
get_workflow_mutation_command,
|
||||
get_workflow_query_service,
|
||||
)
|
||||
|
||||
# 兼容聚合入口只显式列出既有 FastAPI 依赖,不向插件制造新的动态导出规则。
|
||||
__all__ = [
|
||||
"get_agent_chat_service",
|
||||
"get_auth_service",
|
||||
"get_current_active_manage_user",
|
||||
"get_current_active_manage_user_async",
|
||||
"get_current_active_superuser",
|
||||
"get_current_active_superuser_async",
|
||||
"get_current_active_user",
|
||||
"get_current_active_user_async",
|
||||
"get_current_user",
|
||||
"get_current_user_async",
|
||||
"get_dashboard_query_service",
|
||||
"get_delete_subscribe_command",
|
||||
"get_delete_subscriptions_by_identity_command",
|
||||
"get_download_history_mutation_command",
|
||||
"get_history_query_service",
|
||||
"get_mediaserver_query_service",
|
||||
"get_message_query_service",
|
||||
"get_passkey_service",
|
||||
"get_plugin_config_command",
|
||||
"get_search_subscriptions_command",
|
||||
"get_servarr_subscription_service",
|
||||
"get_site_mutation_command",
|
||||
"get_site_query_service",
|
||||
"get_site_sync_query_service",
|
||||
"get_subscription_mutation_service",
|
||||
"get_subscription_query_service",
|
||||
"get_subscription_sync_mutation_service",
|
||||
"get_transfer_history_lookup_service",
|
||||
"get_transfer_history_mutation_command",
|
||||
"get_user_service",
|
||||
"get_workflow_definition_command",
|
||||
"get_workflow_mutation_command",
|
||||
"get_workflow_query_service",
|
||||
]
|
||||
|
||||
+41
-78
@@ -33,6 +33,7 @@ from app.schemas.message import AgentWebChoiceRequest as _SchemaAgentWebChoiceRe
|
||||
from app.schemas.message import Message as _SchemaMessage
|
||||
from app.schemas.response import Response as _SchemaResponse
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.api.presentation.sse import build_sse_error_response, build_sse_response
|
||||
from app.agent.contracts import ReplyMode, build_display_message
|
||||
from app.agent.llm.capability import AgentCapabilityManager
|
||||
from app.agent.mcp import agent_mcp_manager
|
||||
@@ -45,7 +46,8 @@ from app.command import Command
|
||||
from app.runtime.config import global_vars, settings
|
||||
from app.runtime.events import Event, EventManager
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.api.deps import get_agent_chat_service, get_current_active_user
|
||||
from app.api.dependencies.agent import get_agent_chat_service
|
||||
from app.api.dependencies.auth import get_current_active_user
|
||||
from app.application.messaging.chat import (
|
||||
AgentChatRecord,
|
||||
AgentChatService,
|
||||
@@ -694,6 +696,21 @@ def _build_web_agent_sse(
|
||||
return f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
|
||||
|
||||
|
||||
def _build_web_agent_error_response(
|
||||
message: str,
|
||||
*,
|
||||
locale: Optional[str],
|
||||
) -> StreamingResponse:
|
||||
"""Map a rejected WebAgent request to one terminal SSE error event."""
|
||||
return build_sse_error_response(
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": message},
|
||||
locale=locale,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _sanitize_web_agent_upload_name(
|
||||
filename: Optional[str], mime_type: Optional[str] = None
|
||||
) -> str:
|
||||
@@ -1965,15 +1982,9 @@ async def web_agent_stream(
|
||||
getattr(request, "headers", {}).get("X-MoviePilot-Agent-Interaction") == "1"
|
||||
)
|
||||
if is_secret_confirmation_control and not protected_transport_supported:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": "当前客户端不支持安全交付敏感设置,未执行操作。"},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
"当前客户端不支持安全交付敏感设置,未执行操作。",
|
||||
locale=locale,
|
||||
)
|
||||
is_traditional_message = (
|
||||
_is_web_agent_traditional_message(prompt)
|
||||
@@ -1982,27 +1993,15 @@ async def web_agent_stream(
|
||||
if is_traditional_message:
|
||||
denied_message = _ensure_web_agent_command_allowed(current_user)
|
||||
if denied_message:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": denied_message},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
denied_message,
|
||||
locale=locale,
|
||||
)
|
||||
unknown_command_message = _get_web_agent_unknown_command_message(prompt)
|
||||
if unknown_command_message:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": unknown_command_message},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
unknown_command_message,
|
||||
locale=locale,
|
||||
)
|
||||
|
||||
user_attachments = _build_web_agent_input_attachments(
|
||||
@@ -2089,39 +2088,19 @@ async def web_agent_stream(
|
||||
return
|
||||
yield _build_web_agent_sse("done", {}, locale=locale)
|
||||
|
||||
return StreamingResponse(
|
||||
traditional_event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
return build_sse_response(traditional_event_generator())
|
||||
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": "智能助手未启用,请先在系统设置中开启。"},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
"智能助手未启用,请先在系统设置中开启。",
|
||||
locale=locale,
|
||||
)
|
||||
|
||||
manager = get_running_agent_manager()
|
||||
if manager is None:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": "智能助手服务尚未就绪,请稍后重试。"},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
"智能助手服务尚未就绪,请稍后重试。",
|
||||
locale=locale,
|
||||
)
|
||||
|
||||
transcript = _transcribe_web_agent_audio_refs(payload.audio_refs or [])
|
||||
@@ -2129,26 +2108,14 @@ async def web_agent_stream(
|
||||
display_prompt = _merge_web_agent_prompt_with_transcript(display_prompt, transcript)
|
||||
has_audio_input = bool(transcript)
|
||||
if not prompt and payload.audio_refs and not payload.images and not payload.files:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": "语音识别失败,请稍后重试。"},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
"语音识别失败,请稍后重试。",
|
||||
locale=locale,
|
||||
)
|
||||
if not prompt and not payload.images and not payload.files and not payload.audio_refs:
|
||||
return StreamingResponse(
|
||||
iter([
|
||||
_build_web_agent_sse(
|
||||
"error",
|
||||
{"message": "请输入要发送给智能助手的内容或选择附件。"},
|
||||
locale=locale,
|
||||
)
|
||||
]),
|
||||
media_type="text/event-stream",
|
||||
return _build_web_agent_error_response(
|
||||
"请输入要发送给智能助手的内容或选择附件。",
|
||||
locale=locale,
|
||||
)
|
||||
|
||||
MessageChain().bind_user_session(str(current_user.id), session_id)
|
||||
@@ -2314,13 +2281,9 @@ async def web_agent_stream(
|
||||
await event_publisher.aclose()
|
||||
# 客户端断线后保留 Agent 继续执行;发布器关闭后不再接受受保护结果。
|
||||
|
||||
return StreamingResponse(
|
||||
return build_sse_response(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
**(
|
||||
{"X-MoviePilot-Agent-Control": "secret-confirmation"}
|
||||
if is_secret_confirmation_control
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from typing import AsyncIterator, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Header, Security
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.schemas.openai import AnthropicErrorDetail as _SchemaAnthropicErrorDetail
|
||||
from app.schemas.openai import AnthropicErrorResponse as _SchemaAnthropicErrorResponse
|
||||
@@ -21,6 +20,7 @@ from app.api.openai_utils import (
|
||||
build_prompt,
|
||||
build_session_id,
|
||||
)
|
||||
from app.api.presentation.sse import build_sse_response, encode_named_event
|
||||
from app.agent.runtime_loader import get_running_agent_manager
|
||||
from app.runtime.config import settings
|
||||
from app.adapters.web.security.access import anthropic_api_key_header
|
||||
@@ -97,26 +97,71 @@ async def _stream_anthropic_response(
|
||||
|
||||
task = asyncio.create_task(_run_agent())
|
||||
try:
|
||||
yield f"event: message_start\ndata: {json.dumps({'type': 'message_start', 'message': {'id': message_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': MODEL_ID, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': 0, 'output_tokens': 0}}}, ensure_ascii=False)}\n\n"
|
||||
yield f"event: content_block_start\ndata: {json.dumps({'type': 'content_block_start', 'index': 0, 'content_block': {'type': 'text', 'text': ''}}, ensure_ascii=False)}\n\n"
|
||||
yield encode_named_event(
|
||||
"message_start",
|
||||
{
|
||||
"type": "message_start",
|
||||
"message": {
|
||||
"id": message_id,
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [],
|
||||
"model": MODEL_ID,
|
||||
"stop_reason": None,
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0},
|
||||
},
|
||||
},
|
||||
)
|
||||
yield encode_named_event(
|
||||
"content_block_start",
|
||||
{
|
||||
"type": "content_block_start",
|
||||
"index": 0,
|
||||
"content_block": {"type": "text", "text": ""},
|
||||
},
|
||||
)
|
||||
while True:
|
||||
item = await event_queue.get()
|
||||
if item is None:
|
||||
break
|
||||
if isinstance(item, dict) and item.get("error"):
|
||||
yield (
|
||||
"event: error\n"
|
||||
f"data: {json.dumps({'type': 'error', 'error': {'type': 'api_error', 'message': str(item['error'])}}, ensure_ascii=False)}\n\n"
|
||||
yield encode_named_event(
|
||||
"error",
|
||||
{
|
||||
"type": "error",
|
||||
"error": {
|
||||
"type": "api_error",
|
||||
"message": str(item["error"]),
|
||||
},
|
||||
},
|
||||
)
|
||||
yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'}, ensure_ascii=False)}\n\n"
|
||||
yield encode_named_event("message_stop", {"type": "message_stop"})
|
||||
return
|
||||
text = str(item or "")
|
||||
if not text:
|
||||
continue
|
||||
yield f"event: content_block_delta\ndata: {json.dumps({'type': 'content_block_delta', 'index': 0, 'delta': {'type': 'text_delta', 'text': text}}, ensure_ascii=False)}\n\n"
|
||||
yield f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': 0}, ensure_ascii=False)}\n\n"
|
||||
yield f"event: message_delta\ndata: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'usage': {'output_tokens': 0}}, ensure_ascii=False)}\n\n"
|
||||
yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'}, ensure_ascii=False)}\n\n"
|
||||
yield encode_named_event(
|
||||
"content_block_delta",
|
||||
{
|
||||
"type": "content_block_delta",
|
||||
"index": 0,
|
||||
"delta": {"type": "text_delta", "text": text},
|
||||
},
|
||||
)
|
||||
yield encode_named_event(
|
||||
"content_block_stop",
|
||||
{"type": "content_block_stop", "index": 0},
|
||||
)
|
||||
yield encode_named_event(
|
||||
"message_delta",
|
||||
{
|
||||
"type": "message_delta",
|
||||
"delta": {"stop_reason": "end_turn", "stop_sequence": None},
|
||||
"usage": {"output_tokens": 0},
|
||||
},
|
||||
)
|
||||
yield encode_named_event("message_stop", {"type": "message_stop"})
|
||||
finally:
|
||||
await manager.clear_session(session_id=session_id, user_id=user_id)
|
||||
if not task.done():
|
||||
@@ -172,7 +217,7 @@ async def messages(
|
||||
session_seed = anthropic_version or "anthropic"
|
||||
session_id = build_session_id(f"{session_seed}:{uuid.uuid4().hex}", SESSION_PREFIX)
|
||||
if payload.stream:
|
||||
return StreamingResponse(
|
||||
return build_sse_response(
|
||||
_stream_anthropic_response(
|
||||
manager=manager,
|
||||
session_id=session_id,
|
||||
@@ -180,12 +225,6 @@ async def messages(
|
||||
prompt=prompt,
|
||||
images=images,
|
||||
),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
collected_messages = []
|
||||
|
||||
@@ -8,7 +8,7 @@ from app.schemas.user import AuthProviderInfo as _SchemaAuthProviderInfo
|
||||
from app.api.response import RAW_RESPONSE_OPENAPI_KEY, ResponseAPIRouter
|
||||
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
|
||||
from app.api.dependencies.auth import get_auth_service
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ from app.chain.dashboard import DashboardChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.config import settings
|
||||
from app.adapters.web.security.access import verify_apitoken
|
||||
from app.api.deps import get_current_active_superuser, get_dashboard_query_service
|
||||
from app.api.dependencies.auth import get_current_active_superuser
|
||||
from app.api.dependencies.history import get_dashboard_query_service
|
||||
from app.application.dashboard import DashboardQueryService
|
||||
from app.schemas.types import StorageAction
|
||||
from app.application.directory import DirectoryHelper
|
||||
|
||||
@@ -28,7 +28,8 @@ 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.api.dependencies.auth import get_current_active_user
|
||||
from app.api.dependencies.site import get_site_sync_query_service
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
|
||||
@@ -21,9 +21,11 @@ from app.agent.prompt.transfer_redo import (
|
||||
)
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_superuser,
|
||||
)
|
||||
from app.api.dependencies.history import (
|
||||
get_download_history_mutation_command,
|
||||
get_history_query_service,
|
||||
get_transfer_history_mutation_command,
|
||||
|
||||
@@ -6,7 +6,7 @@ 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.api.deps import get_current_active_superuser_async
|
||||
from app.api.dependencies.auth import get_current_active_superuser_async
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
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.api.dependencies.auth import (
|
||||
get_current_active_superuser,
|
||||
get_current_active_user,
|
||||
)
|
||||
from app.schemas.category import CategoryConfig
|
||||
from app.schemas.event import MediaSourceInfo as _SchemaMediaSourceInfo
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource, MediaType
|
||||
|
||||
@@ -21,7 +21,7 @@ from app.domain.metainfo import MetaInfo
|
||||
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.api.dependencies.history 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
|
||||
|
||||
@@ -22,7 +22,8 @@ from app.runtime.config import settings, global_vars
|
||||
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.api.dependencies.agent import get_message_query_service
|
||||
from app.api.dependencies.auth import get_current_active_superuser
|
||||
from app.application.messaging.message import MessageQueryService
|
||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
||||
from app.runtime.log import logger
|
||||
|
||||
@@ -30,7 +30,7 @@ from app.application.security.passkeys import (
|
||||
PasskeyService,
|
||||
)
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_user,
|
||||
get_current_active_user_async,
|
||||
get_user_service,
|
||||
|
||||
@@ -15,7 +15,7 @@ from app.chain.recommend import RecommendChain
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
from app.domain.context import MusicAlbumInfo, MusicArtistInfo, MusicInfo
|
||||
from app.adapters.web.security.access import verify_token
|
||||
from app.api.deps import get_current_active_superuser_async
|
||||
from app.api.dependencies.auth import get_current_active_superuser_async
|
||||
from app.chain.listenbrainz import (
|
||||
LISTENBRAINZ_CHART_RANGES,
|
||||
LISTENBRAINZ_FRESH_MAX_DAYS,
|
||||
|
||||
@@ -6,7 +6,7 @@ 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.api.deps import get_current_active_superuser
|
||||
from app.api.dependencies.auth import get_current_active_superuser
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from threading import Lock
|
||||
from typing import AsyncIterator, List, Optional, Tuple
|
||||
|
||||
from fastapi import APIRouter, Request, Security
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
|
||||
from app.schemas.openai import OpenAIChatCompletionResponse as _SchemaOpenAIChatCompletionResponse
|
||||
@@ -26,6 +25,7 @@ from app.api.openai_utils import (
|
||||
build_responses_input,
|
||||
build_session_id,
|
||||
)
|
||||
from app.api.presentation.sse import build_sse_response, encode_data_event
|
||||
from app.agent.runtime_loader import (
|
||||
get_moviepilot_agent_type,
|
||||
get_running_agent_manager,
|
||||
@@ -216,7 +216,8 @@ def _get_collecting_agent_type() -> type:
|
||||
|
||||
|
||||
def _sse_payload(data: dict) -> str:
|
||||
return f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
|
||||
"""保留旧测试入口并委托独立 OpenAI SSE wire mapper。"""
|
||||
return encode_data_event(data)
|
||||
|
||||
|
||||
async def _stream_response(
|
||||
@@ -519,7 +520,7 @@ async def chat_completions(
|
||||
session_id = build_session_id(session_key, SESSION_PREFIX)
|
||||
username = str(payload.user or "openai-client")
|
||||
if payload.stream:
|
||||
return StreamingResponse(
|
||||
return build_sse_response(
|
||||
_stream_response(
|
||||
manager=manager,
|
||||
session_id=session_id,
|
||||
@@ -529,12 +530,6 @@ async def chat_completions(
|
||||
images=images,
|
||||
cleanup_session=not use_server_session,
|
||||
),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
collected_messages = []
|
||||
|
||||
@@ -46,9 +46,11 @@ from app.adapters.web.security.access import (
|
||||
)
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
)
|
||||
from app.api.dependencies.plugin import (
|
||||
get_plugin_config_command,
|
||||
)
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
|
||||
@@ -26,11 +26,13 @@ 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 (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_manage_user_async,
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
)
|
||||
from app.api.dependencies.site import (
|
||||
get_site_mutation_command,
|
||||
get_site_query_service,
|
||||
get_site_sync_query_service,
|
||||
|
||||
@@ -16,7 +16,7 @@ from app.chain.storage import StorageChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import settings
|
||||
from app.api.principal import ApiPrincipal
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_superuser,
|
||||
)
|
||||
|
||||
@@ -36,9 +36,11 @@ from app.application.subscription.mutation import (
|
||||
SubscriptionMutationService,
|
||||
)
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_user,
|
||||
get_current_active_user_async,
|
||||
)
|
||||
from app.api.dependencies.subscription import (
|
||||
get_delete_subscribe_command,
|
||||
get_delete_subscriptions_by_identity_command,
|
||||
get_search_subscriptions_command,
|
||||
|
||||
@@ -42,7 +42,11 @@ 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.api.dependencies.auth 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
|
||||
from app.adapters.external.market import (
|
||||
|
||||
@@ -14,7 +14,7 @@ from app.chain.tmdb import TmdbChain
|
||||
from app.runtime.config import settings
|
||||
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.api.dependencies.auth import get_current_active_superuser_async
|
||||
from app.schemas.types import MediaType, SystemConfigKey
|
||||
|
||||
router = ResponseAPIRouter()
|
||||
|
||||
@@ -12,7 +12,10 @@ 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.api.deps import get_current_active_superuser, get_current_active_superuser_async
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_superuser,
|
||||
get_current_active_superuser_async,
|
||||
)
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaSource,
|
||||
|
||||
@@ -19,10 +19,8 @@ from app.chain.media import MediaChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import settings, global_vars
|
||||
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.api.dependencies.auth import get_current_active_manage_user
|
||||
from app.api.dependencies.history import get_transfer_history_lookup_service
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.history import TransferHistoryLookupService
|
||||
from app.runtime.log import logger
|
||||
@@ -304,12 +302,26 @@ def manual_transfer(
|
||||
_: object = Depends(get_current_active_manage_user),
|
||||
) -> Any:
|
||||
"""
|
||||
手动转移,文件或历史记录,支持自定义剧集识别格式
|
||||
解析手动整理 HTTP 请求并委托兼容用例处理器。
|
||||
|
||||
:param transer_item: 手工整理项
|
||||
:param background: 后台运行
|
||||
:param history_query: 整理历史投影服务
|
||||
:param _: Token校验
|
||||
"""
|
||||
return _execute_manual_transfer(
|
||||
transer_item=transer_item,
|
||||
background=background,
|
||||
history_query=history_query,
|
||||
)
|
||||
|
||||
|
||||
def _execute_manual_transfer(
|
||||
transer_item: ManualTransferItem,
|
||||
background: Optional[bool],
|
||||
history_query: TransferHistoryLookupService,
|
||||
) -> Any:
|
||||
"""执行历史恢复、批量预览与 TransferChain 兼容编排。"""
|
||||
force = False
|
||||
downloader = None
|
||||
download_hash = None
|
||||
|
||||
@@ -13,7 +13,7 @@ from app.schemas.user import UserUpdate as _SchemaUserUpdate
|
||||
from app.api.response import ResponseAPIRouter
|
||||
from app.application.security.token import PasswordTooLongError, get_password_hash
|
||||
from app.application.security.user import UserService
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_superuser_async,
|
||||
get_current_active_user_async,
|
||||
get_current_active_user,
|
||||
|
||||
@@ -17,9 +17,11 @@ from app.application.workflow import (
|
||||
from app.chain.workflow import WorkflowChain
|
||||
from app.application.plugin.runtime import get_plugin_manager as PluginManager
|
||||
from app.workflow import WorkFlowManager
|
||||
from app.api.deps import (
|
||||
from app.api.dependencies.auth import (
|
||||
get_current_active_manage_user,
|
||||
get_current_active_manage_user_async,
|
||||
)
|
||||
from app.api.dependencies.workflow import (
|
||||
get_workflow_definition_command,
|
||||
get_workflow_mutation_command,
|
||||
get_workflow_query_service,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""API presentation helpers isolated from domain dependency assembly."""
|
||||
@@ -0,0 +1,45 @@
|
||||
"""SSE wire mapping and response lifecycle helpers."""
|
||||
|
||||
import json
|
||||
from collections.abc import AsyncIterable, Iterable
|
||||
from typing import Any
|
||||
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
|
||||
SSE_HEADERS = {
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
}
|
||||
|
||||
|
||||
def encode_data_event(payload: dict[str, Any]) -> str:
|
||||
"""Encode an OpenAI-style unnamed SSE data event."""
|
||||
return f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
|
||||
|
||||
|
||||
def encode_named_event(event: str, payload: dict[str, Any]) -> str:
|
||||
"""Encode a named SSE event without applying a response envelope."""
|
||||
return (
|
||||
f"event: {event}\n"
|
||||
f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
|
||||
)
|
||||
|
||||
|
||||
def build_sse_response(
|
||||
content: AsyncIterable[str] | Iterable[str],
|
||||
*,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> StreamingResponse:
|
||||
"""Build a non-buffered SSE response and merge protocol-specific headers."""
|
||||
return StreamingResponse(
|
||||
content,
|
||||
media_type="text/event-stream",
|
||||
headers={**SSE_HEADERS, **(headers or {})},
|
||||
)
|
||||
|
||||
|
||||
def build_sse_error_response(payload: str) -> StreamingResponse:
|
||||
"""Build a one-event SSE error response using the common transport policy."""
|
||||
return build_sse_response(iter([payload]))
|
||||
+1
-1
@@ -19,7 +19,7 @@ from app.domain.context import MediaInfo
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.application.servarr import ServarrSubscription, ServarrSubscriptionService
|
||||
from app.adapters.web.security.access import verify_apikey
|
||||
from app.api.deps import get_servarr_subscription_service
|
||||
from app.api.dependencies.subscription import get_servarr_subscription_service
|
||||
from app.schemas.servarr import RadarrMovie
|
||||
from app.schemas.servarr import SonarrSeries
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
> 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本
|
||||
> 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文
|
||||
> 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md`
|
||||
> 实施进度:阶段 0(ARCH-201~203)、阶段 1(ARCH-210~212)、阶段 2(ARCH-220~222)与 ARCH-230 已完成,后续任务按 ID 独立提交和回滚
|
||||
> 实施进度:阶段 0(ARCH-201~203)、阶段 1(ARCH-210~212)、阶段 2(ARCH-220~222)与 ARCH-230~231 已完成,后续任务按 ID 独立提交和回滚
|
||||
|
||||
## 1. 结论先行
|
||||
|
||||
@@ -473,6 +473,22 @@ app/api/dependencies/ # 按领域拆分依赖工厂
|
||||
|
||||
**优先切片**:`manual_transfer()`、`web_agent_stream()`、OpenAI/Anthropic streaming adapter。
|
||||
|
||||
**实施记录(2026-08-21)**:
|
||||
|
||||
- `app/api/deps.py` 已由 524 行集中装配点收敛为 88 行兼容聚合入口;认证、Agent、订阅、站点、
|
||||
工作流、历史和插件依赖分别由 `app/api/dependencies/` 下的领域模块拥有。宿主 API 端点全部改为
|
||||
直接导入领域依赖,旧入口只为外部兼容消费者保留。
|
||||
- 新增 `app/api/presentation/sse.py`,统一 non-buffered SSE transport 策略,并分别提供 unnamed data
|
||||
与 named event wire mapper。WebAgent、OpenAI 和 Anthropic 继续保留各自协议 payload 与错误结构,
|
||||
不进入通用 `Response` 包装。
|
||||
- `manual_transfer()` 已缩为 HTTP/鉴权/依赖入口,历史恢复、批量预览和旧 `TransferChain` 参数兼容
|
||||
由内部处理器承接;WebAgent 的拒绝响应、stream headers 与协议映射已从主控制流抽离。长生命周期
|
||||
generator 仍保留在端点模块,因为它直接拥有 request disconnect、后台 task cancel 与敏感结果关闭时序,
|
||||
后续只能在保持现有取消测试的前提下继续下沉。
|
||||
- 125 个鉴权、手动整理、WebAgent、OpenAI/Anthropic 生命周期、API 响应和 typed runtime 专项测试通过;
|
||||
61 个架构/基线 CLI 测试通过。依赖基线变化只反映集中边拆为领域边,runtime contract 变化只反映
|
||||
dependency callable 的新模块路径;禁止边与插件 raw API 均未变化。
|
||||
|
||||
#### ARCH-232:配置快照与窄配置端口
|
||||
|
||||
**目标**:阻止 `settings` 和 `SystemConfigOper()` 继续扩散,不要求一次清除 180 个文件。
|
||||
|
||||
+188
-79
@@ -13,8 +13,8 @@
|
||||
"runtime_to_db": [],
|
||||
"workflow_to_db": []
|
||||
},
|
||||
"edge_count": 6105,
|
||||
"edge_sha256": "47fd6792530be771c9854d3f2951097d3b110cd46b1cab11643a070f3b11299c",
|
||||
"edge_count": 6203,
|
||||
"edge_sha256": "75ac7a20854abb707ea6c851326a183873a64e93f67053ffcd79ca5b01a105f6",
|
||||
"edges": [
|
||||
"app -> app.runtime",
|
||||
"app -> app.runtime.compat",
|
||||
@@ -1480,60 +1480,119 @@
|
||||
"app.api.context -> app.application.messaging.chat",
|
||||
"app.api.context -> app.startup",
|
||||
"app.api.context -> app.startup.context",
|
||||
"app.api.deps -> app.adapters",
|
||||
"app.api.deps -> app.adapters.external",
|
||||
"app.api.deps -> app.adapters.external.server",
|
||||
"app.api.deps -> app.adapters.web",
|
||||
"app.api.deps -> app.adapters.web.security",
|
||||
"app.api.deps -> app.adapters.web.security.access",
|
||||
"app.api.dependencies.agent -> app.api",
|
||||
"app.api.dependencies.agent -> app.api.context",
|
||||
"app.api.dependencies.agent -> app.api.data",
|
||||
"app.api.dependencies.agent -> app.api.dependencies",
|
||||
"app.api.dependencies.agent -> app.api.dependencies.data",
|
||||
"app.api.dependencies.agent -> app.application",
|
||||
"app.api.dependencies.agent -> app.application.messaging",
|
||||
"app.api.dependencies.agent -> app.application.messaging.chat",
|
||||
"app.api.dependencies.agent -> app.application.messaging.message",
|
||||
"app.api.dependencies.auth -> app.adapters",
|
||||
"app.api.dependencies.auth -> app.adapters.web",
|
||||
"app.api.dependencies.auth -> app.adapters.web.security",
|
||||
"app.api.dependencies.auth -> app.adapters.web.security.access",
|
||||
"app.api.dependencies.auth -> app.api",
|
||||
"app.api.dependencies.auth -> app.api.data",
|
||||
"app.api.dependencies.auth -> app.api.dependencies",
|
||||
"app.api.dependencies.auth -> app.api.dependencies.data",
|
||||
"app.api.dependencies.auth -> app.application",
|
||||
"app.api.dependencies.auth -> app.application.security",
|
||||
"app.api.dependencies.auth -> app.application.security.auth",
|
||||
"app.api.dependencies.auth -> app.application.security.passkeys",
|
||||
"app.api.dependencies.auth -> app.application.security.user",
|
||||
"app.api.dependencies.auth -> app.schemas",
|
||||
"app.api.dependencies.auth -> app.schemas.token",
|
||||
"app.api.dependencies.data -> app.api",
|
||||
"app.api.dependencies.data -> app.api.data",
|
||||
"app.api.dependencies.history -> app.api",
|
||||
"app.api.dependencies.history -> app.api.data",
|
||||
"app.api.dependencies.history -> app.api.dependencies",
|
||||
"app.api.dependencies.history -> app.api.dependencies.data",
|
||||
"app.api.dependencies.history -> app.application",
|
||||
"app.api.dependencies.history -> app.application.dashboard",
|
||||
"app.api.dependencies.history -> app.application.history",
|
||||
"app.api.dependencies.history -> app.application.mediaserver",
|
||||
"app.api.dependencies.history -> app.chain",
|
||||
"app.api.dependencies.history -> app.chain.dashboard",
|
||||
"app.api.dependencies.history -> app.chain.storage",
|
||||
"app.api.dependencies.history -> app.runtime",
|
||||
"app.api.dependencies.history -> app.runtime.events",
|
||||
"app.api.dependencies.history -> app.schemas",
|
||||
"app.api.dependencies.history -> app.schemas.types",
|
||||
"app.api.dependencies.history -> app.schemas.workflow",
|
||||
"app.api.dependencies.plugin -> app.application",
|
||||
"app.api.dependencies.plugin -> app.application.commands",
|
||||
"app.api.dependencies.plugin -> app.application.plugin",
|
||||
"app.api.dependencies.plugin -> app.application.plugin.config",
|
||||
"app.api.dependencies.plugin -> app.application.plugin.routes",
|
||||
"app.api.dependencies.plugin -> app.application.plugin.runtime",
|
||||
"app.api.dependencies.plugin -> app.application.scheduling",
|
||||
"app.api.dependencies.plugin -> app.runtime",
|
||||
"app.api.dependencies.plugin -> app.runtime.events",
|
||||
"app.api.dependencies.plugin -> app.schemas",
|
||||
"app.api.dependencies.plugin -> app.schemas.event",
|
||||
"app.api.dependencies.plugin -> app.schemas.types",
|
||||
"app.api.dependencies.site -> app.api",
|
||||
"app.api.dependencies.site -> app.api.data",
|
||||
"app.api.dependencies.site -> app.api.dependencies",
|
||||
"app.api.dependencies.site -> app.api.dependencies.data",
|
||||
"app.api.dependencies.site -> app.application",
|
||||
"app.api.dependencies.site -> app.application.site",
|
||||
"app.api.dependencies.site -> app.application.site.mutation",
|
||||
"app.api.dependencies.site -> app.application.site.query",
|
||||
"app.api.dependencies.site -> app.domain",
|
||||
"app.api.dependencies.site -> app.domain.site",
|
||||
"app.api.dependencies.site -> app.foundation",
|
||||
"app.api.dependencies.site -> app.foundation.url",
|
||||
"app.api.dependencies.site -> app.runtime",
|
||||
"app.api.dependencies.site -> app.runtime.events",
|
||||
"app.api.dependencies.site -> app.schemas",
|
||||
"app.api.dependencies.site -> app.schemas.types",
|
||||
"app.api.dependencies.subscription -> app.adapters",
|
||||
"app.api.dependencies.subscription -> app.adapters.external",
|
||||
"app.api.dependencies.subscription -> app.adapters.external.server",
|
||||
"app.api.dependencies.subscription -> app.api",
|
||||
"app.api.dependencies.subscription -> app.api.data",
|
||||
"app.api.dependencies.subscription -> app.api.dependencies",
|
||||
"app.api.dependencies.subscription -> app.api.dependencies.data",
|
||||
"app.api.dependencies.subscription -> app.application",
|
||||
"app.api.dependencies.subscription -> app.application.scheduling",
|
||||
"app.api.dependencies.subscription -> app.application.servarr",
|
||||
"app.api.dependencies.subscription -> app.application.subscription",
|
||||
"app.api.dependencies.subscription -> app.application.subscription.delete",
|
||||
"app.api.dependencies.subscription -> app.application.subscription.identity",
|
||||
"app.api.dependencies.subscription -> app.application.subscription.mutation",
|
||||
"app.api.dependencies.subscription -> app.application.subscription.query",
|
||||
"app.api.dependencies.subscription -> app.application.subscription.search",
|
||||
"app.api.dependencies.subscription -> app.runtime",
|
||||
"app.api.dependencies.subscription -> app.runtime.events",
|
||||
"app.api.dependencies.subscription -> app.runtime.log",
|
||||
"app.api.dependencies.subscription -> app.schemas",
|
||||
"app.api.dependencies.subscription -> app.schemas.types",
|
||||
"app.api.dependencies.workflow -> app.adapters",
|
||||
"app.api.dependencies.workflow -> app.adapters.external",
|
||||
"app.api.dependencies.workflow -> app.adapters.external.server",
|
||||
"app.api.dependencies.workflow -> app.api",
|
||||
"app.api.dependencies.workflow -> app.api.data",
|
||||
"app.api.dependencies.workflow -> app.api.dependencies",
|
||||
"app.api.dependencies.workflow -> app.api.dependencies.data",
|
||||
"app.api.dependencies.workflow -> app.application",
|
||||
"app.api.dependencies.workflow -> app.application.scheduling",
|
||||
"app.api.dependencies.workflow -> app.application.workflow",
|
||||
"app.api.dependencies.workflow -> app.runtime",
|
||||
"app.api.dependencies.workflow -> app.runtime.config",
|
||||
"app.api.dependencies.workflow -> app.workflow",
|
||||
"app.api.deps -> app.api",
|
||||
"app.api.deps -> app.api.context",
|
||||
"app.api.deps -> app.api.data",
|
||||
"app.api.deps -> app.application",
|
||||
"app.api.deps -> app.application.commands",
|
||||
"app.api.deps -> app.application.dashboard",
|
||||
"app.api.deps -> app.application.history",
|
||||
"app.api.deps -> app.application.mediaserver",
|
||||
"app.api.deps -> app.application.messaging",
|
||||
"app.api.deps -> app.application.messaging.chat",
|
||||
"app.api.deps -> app.application.messaging.message",
|
||||
"app.api.deps -> app.application.plugin",
|
||||
"app.api.deps -> app.application.plugin.config",
|
||||
"app.api.deps -> app.application.plugin.routes",
|
||||
"app.api.deps -> app.application.plugin.runtime",
|
||||
"app.api.deps -> app.application.scheduling",
|
||||
"app.api.deps -> app.application.security",
|
||||
"app.api.deps -> app.application.security.auth",
|
||||
"app.api.deps -> app.application.security.passkeys",
|
||||
"app.api.deps -> app.application.security.user",
|
||||
"app.api.deps -> app.application.servarr",
|
||||
"app.api.deps -> app.application.site",
|
||||
"app.api.deps -> app.application.site.mutation",
|
||||
"app.api.deps -> app.application.site.query",
|
||||
"app.api.deps -> app.application.subscription",
|
||||
"app.api.deps -> app.application.subscription.delete",
|
||||
"app.api.deps -> app.application.subscription.identity",
|
||||
"app.api.deps -> app.application.subscription.mutation",
|
||||
"app.api.deps -> app.application.subscription.query",
|
||||
"app.api.deps -> app.application.subscription.search",
|
||||
"app.api.deps -> app.application.workflow",
|
||||
"app.api.deps -> app.chain",
|
||||
"app.api.deps -> app.chain.dashboard",
|
||||
"app.api.deps -> app.chain.storage",
|
||||
"app.api.deps -> app.domain",
|
||||
"app.api.deps -> app.domain.site",
|
||||
"app.api.deps -> app.foundation",
|
||||
"app.api.deps -> app.foundation.url",
|
||||
"app.api.deps -> app.runtime",
|
||||
"app.api.deps -> app.runtime.config",
|
||||
"app.api.deps -> app.runtime.events",
|
||||
"app.api.deps -> app.runtime.log",
|
||||
"app.api.deps -> app.schemas",
|
||||
"app.api.deps -> app.schemas.event",
|
||||
"app.api.deps -> app.schemas.token",
|
||||
"app.api.deps -> app.schemas.types",
|
||||
"app.api.deps -> app.schemas.workflow",
|
||||
"app.api.deps -> app.workflow",
|
||||
"app.api.deps -> app.api.dependencies",
|
||||
"app.api.deps -> app.api.dependencies.agent",
|
||||
"app.api.deps -> app.api.dependencies.auth",
|
||||
"app.api.deps -> app.api.dependencies.history",
|
||||
"app.api.deps -> app.api.dependencies.plugin",
|
||||
"app.api.deps -> app.api.dependencies.site",
|
||||
"app.api.deps -> app.api.dependencies.subscription",
|
||||
"app.api.deps -> app.api.dependencies.workflow",
|
||||
"app.api.endpoints.agent -> app.agent",
|
||||
"app.api.endpoints.agent -> app.agent.callback",
|
||||
"app.api.endpoints.agent -> app.agent.contracts",
|
||||
@@ -1542,7 +1601,11 @@
|
||||
"app.api.endpoints.agent -> app.agent.mcp",
|
||||
"app.api.endpoints.agent -> app.agent.runtime_loader",
|
||||
"app.api.endpoints.agent -> app.api",
|
||||
"app.api.endpoints.agent -> app.api.deps",
|
||||
"app.api.endpoints.agent -> app.api.dependencies",
|
||||
"app.api.endpoints.agent -> app.api.dependencies.agent",
|
||||
"app.api.endpoints.agent -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.agent -> app.api.presentation",
|
||||
"app.api.endpoints.agent -> app.api.presentation.sse",
|
||||
"app.api.endpoints.agent -> app.api.principal",
|
||||
"app.api.endpoints.agent -> app.api.response",
|
||||
"app.api.endpoints.agent -> app.application",
|
||||
@@ -1589,12 +1652,15 @@
|
||||
"app.api.endpoints.anthropic -> app.api.endpoints",
|
||||
"app.api.endpoints.anthropic -> app.api.endpoints.openai",
|
||||
"app.api.endpoints.anthropic -> app.api.openai_utils",
|
||||
"app.api.endpoints.anthropic -> app.api.presentation",
|
||||
"app.api.endpoints.anthropic -> app.api.presentation.sse",
|
||||
"app.api.endpoints.anthropic -> app.runtime",
|
||||
"app.api.endpoints.anthropic -> app.runtime.config",
|
||||
"app.api.endpoints.anthropic -> app.schemas",
|
||||
"app.api.endpoints.anthropic -> app.schemas.openai",
|
||||
"app.api.endpoints.auth -> app.api",
|
||||
"app.api.endpoints.auth -> app.api.deps",
|
||||
"app.api.endpoints.auth -> app.api.dependencies",
|
||||
"app.api.endpoints.auth -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.auth -> app.api.response",
|
||||
"app.api.endpoints.auth -> app.application",
|
||||
"app.api.endpoints.auth -> app.application.plugin",
|
||||
@@ -1625,7 +1691,9 @@
|
||||
"app.api.endpoints.dashboard -> app.adapters.web.security",
|
||||
"app.api.endpoints.dashboard -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.dashboard -> app.api",
|
||||
"app.api.endpoints.dashboard -> app.api.deps",
|
||||
"app.api.endpoints.dashboard -> app.api.dependencies",
|
||||
"app.api.endpoints.dashboard -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.dashboard -> app.api.dependencies.history",
|
||||
"app.api.endpoints.dashboard -> app.api.response",
|
||||
"app.api.endpoints.dashboard -> app.application",
|
||||
"app.api.endpoints.dashboard -> app.application.dashboard",
|
||||
@@ -1677,7 +1745,9 @@
|
||||
"app.api.endpoints.download -> app.adapters.web.security",
|
||||
"app.api.endpoints.download -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.download -> app.api",
|
||||
"app.api.endpoints.download -> app.api.deps",
|
||||
"app.api.endpoints.download -> app.api.dependencies",
|
||||
"app.api.endpoints.download -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.download -> app.api.dependencies.site",
|
||||
"app.api.endpoints.download -> app.api.principal",
|
||||
"app.api.endpoints.download -> app.api.response",
|
||||
"app.api.endpoints.download -> app.application",
|
||||
@@ -1718,7 +1788,9 @@
|
||||
"app.api.endpoints.history -> app.agent.prompt.transfer_redo",
|
||||
"app.api.endpoints.history -> app.agent.runtime_loader",
|
||||
"app.api.endpoints.history -> app.api",
|
||||
"app.api.endpoints.history -> app.api.deps",
|
||||
"app.api.endpoints.history -> app.api.dependencies",
|
||||
"app.api.endpoints.history -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.history -> app.api.dependencies.history",
|
||||
"app.api.endpoints.history -> app.api.response",
|
||||
"app.api.endpoints.history -> app.application",
|
||||
"app.api.endpoints.history -> app.application.history",
|
||||
@@ -1735,7 +1807,8 @@
|
||||
"app.api.endpoints.llm -> app.agent.llm",
|
||||
"app.api.endpoints.llm -> app.agent.llm.provider",
|
||||
"app.api.endpoints.llm -> app.api",
|
||||
"app.api.endpoints.llm -> app.api.deps",
|
||||
"app.api.endpoints.llm -> app.api.dependencies",
|
||||
"app.api.endpoints.llm -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.llm -> app.api.response",
|
||||
"app.api.endpoints.llm -> app.schemas",
|
||||
"app.api.endpoints.llm -> app.schemas.common",
|
||||
@@ -1779,7 +1852,8 @@
|
||||
"app.api.endpoints.media -> app.adapters.web.security",
|
||||
"app.api.endpoints.media -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.media -> app.api",
|
||||
"app.api.endpoints.media -> app.api.deps",
|
||||
"app.api.endpoints.media -> app.api.dependencies",
|
||||
"app.api.endpoints.media -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.media -> app.api.response",
|
||||
"app.api.endpoints.media -> app.application",
|
||||
"app.api.endpoints.media -> app.application.plugin",
|
||||
@@ -1811,7 +1885,8 @@
|
||||
"app.api.endpoints.mediaserver -> app.adapters.web.security",
|
||||
"app.api.endpoints.mediaserver -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.mediaserver -> app.api",
|
||||
"app.api.endpoints.mediaserver -> app.api.deps",
|
||||
"app.api.endpoints.mediaserver -> app.api.dependencies",
|
||||
"app.api.endpoints.mediaserver -> app.api.dependencies.history",
|
||||
"app.api.endpoints.mediaserver -> app.api.response",
|
||||
"app.api.endpoints.mediaserver -> app.application",
|
||||
"app.api.endpoints.mediaserver -> app.application.configuration",
|
||||
@@ -1837,7 +1912,9 @@
|
||||
"app.api.endpoints.message -> app.adapters.web.security",
|
||||
"app.api.endpoints.message -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.message -> app.api",
|
||||
"app.api.endpoints.message -> app.api.deps",
|
||||
"app.api.endpoints.message -> app.api.dependencies",
|
||||
"app.api.endpoints.message -> app.api.dependencies.agent",
|
||||
"app.api.endpoints.message -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.message -> app.api.principal",
|
||||
"app.api.endpoints.message -> app.api.response",
|
||||
"app.api.endpoints.message -> app.application",
|
||||
@@ -1861,7 +1938,8 @@
|
||||
"app.api.endpoints.mfa -> app.adapters.web.security",
|
||||
"app.api.endpoints.mfa -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.mfa -> app.api",
|
||||
"app.api.endpoints.mfa -> app.api.deps",
|
||||
"app.api.endpoints.mfa -> app.api.dependencies",
|
||||
"app.api.endpoints.mfa -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.mfa -> app.api.principal",
|
||||
"app.api.endpoints.mfa -> app.api.response",
|
||||
"app.api.endpoints.mfa -> app.application",
|
||||
@@ -1884,7 +1962,8 @@
|
||||
"app.api.endpoints.music -> app.adapters.web.security",
|
||||
"app.api.endpoints.music -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.music -> app.api",
|
||||
"app.api.endpoints.music -> app.api.deps",
|
||||
"app.api.endpoints.music -> app.api.dependencies",
|
||||
"app.api.endpoints.music -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.music -> app.api.response",
|
||||
"app.api.endpoints.music -> app.chain",
|
||||
"app.api.endpoints.music -> app.chain.listenbrainz",
|
||||
@@ -1900,7 +1979,8 @@
|
||||
"app.api.endpoints.music -> app.schemas.transfer",
|
||||
"app.api.endpoints.music -> app.schemas.types",
|
||||
"app.api.endpoints.notification -> app.api",
|
||||
"app.api.endpoints.notification -> app.api.deps",
|
||||
"app.api.endpoints.notification -> app.api.dependencies",
|
||||
"app.api.endpoints.notification -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.notification -> app.api.response",
|
||||
"app.api.endpoints.notification -> app.chain",
|
||||
"app.api.endpoints.notification -> app.chain.notification",
|
||||
@@ -1917,6 +1997,8 @@
|
||||
"app.api.endpoints.openai -> app.agent.runtime_loader",
|
||||
"app.api.endpoints.openai -> app.api",
|
||||
"app.api.endpoints.openai -> app.api.openai_utils",
|
||||
"app.api.endpoints.openai -> app.api.presentation",
|
||||
"app.api.endpoints.openai -> app.api.presentation.sse",
|
||||
"app.api.endpoints.openai -> app.runtime",
|
||||
"app.api.endpoints.openai -> app.runtime.config",
|
||||
"app.api.endpoints.openai -> app.schemas",
|
||||
@@ -1933,7 +2015,9 @@
|
||||
"app.api.endpoints.plugin -> app.adapters.web.security",
|
||||
"app.api.endpoints.plugin -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.plugin -> app.api",
|
||||
"app.api.endpoints.plugin -> app.api.deps",
|
||||
"app.api.endpoints.plugin -> app.api.dependencies",
|
||||
"app.api.endpoints.plugin -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.plugin -> app.api.dependencies.plugin",
|
||||
"app.api.endpoints.plugin -> app.api.principal",
|
||||
"app.api.endpoints.plugin -> app.api.response",
|
||||
"app.api.endpoints.plugin -> app.application",
|
||||
@@ -2005,7 +2089,9 @@
|
||||
"app.api.endpoints.site -> app.adapters.web.security",
|
||||
"app.api.endpoints.site -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.site -> app.api",
|
||||
"app.api.endpoints.site -> app.api.deps",
|
||||
"app.api.endpoints.site -> app.api.dependencies",
|
||||
"app.api.endpoints.site -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.site -> app.api.dependencies.site",
|
||||
"app.api.endpoints.site -> app.api.endpoints",
|
||||
"app.api.endpoints.site -> app.api.endpoints.plugin",
|
||||
"app.api.endpoints.site -> app.api.principal",
|
||||
@@ -2035,7 +2121,8 @@
|
||||
"app.api.endpoints.site -> app.schemas.types",
|
||||
"app.api.endpoints.site -> app.schemas.workflow",
|
||||
"app.api.endpoints.storage -> app.api",
|
||||
"app.api.endpoints.storage -> app.api.deps",
|
||||
"app.api.endpoints.storage -> app.api.dependencies",
|
||||
"app.api.endpoints.storage -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.storage -> app.api.principal",
|
||||
"app.api.endpoints.storage -> app.api.response",
|
||||
"app.api.endpoints.storage -> app.chain",
|
||||
@@ -2059,7 +2146,9 @@
|
||||
"app.api.endpoints.subscribe -> app.adapters.web.security",
|
||||
"app.api.endpoints.subscribe -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.subscribe -> app.api",
|
||||
"app.api.endpoints.subscribe -> app.api.deps",
|
||||
"app.api.endpoints.subscribe -> app.api.dependencies",
|
||||
"app.api.endpoints.subscribe -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.subscribe -> app.api.dependencies.subscription",
|
||||
"app.api.endpoints.subscribe -> app.api.principal",
|
||||
"app.api.endpoints.subscribe -> app.api.response",
|
||||
"app.api.endpoints.subscribe -> app.application",
|
||||
@@ -2103,7 +2192,8 @@
|
||||
"app.api.endpoints.system -> app.agent.llm",
|
||||
"app.api.endpoints.system -> app.agent.llm.server_tools",
|
||||
"app.api.endpoints.system -> app.api",
|
||||
"app.api.endpoints.system -> app.api.deps",
|
||||
"app.api.endpoints.system -> app.api.dependencies",
|
||||
"app.api.endpoints.system -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.system -> app.api.principal",
|
||||
"app.api.endpoints.system -> app.api.response",
|
||||
"app.api.endpoints.system -> app.application",
|
||||
@@ -2147,7 +2237,8 @@
|
||||
"app.api.endpoints.tmdb -> app.adapters.web.security",
|
||||
"app.api.endpoints.tmdb -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.tmdb -> app.api",
|
||||
"app.api.endpoints.tmdb -> app.api.deps",
|
||||
"app.api.endpoints.tmdb -> app.api.dependencies",
|
||||
"app.api.endpoints.tmdb -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.tmdb -> app.api.response",
|
||||
"app.api.endpoints.tmdb -> app.application",
|
||||
"app.api.endpoints.tmdb -> app.application.configuration",
|
||||
@@ -2163,7 +2254,8 @@
|
||||
"app.api.endpoints.tmdb -> app.schemas.types",
|
||||
"app.api.endpoints.tmdb -> app.schemas.workflow",
|
||||
"app.api.endpoints.torrent -> app.api",
|
||||
"app.api.endpoints.torrent -> app.api.deps",
|
||||
"app.api.endpoints.torrent -> app.api.dependencies",
|
||||
"app.api.endpoints.torrent -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.torrent -> app.api.response",
|
||||
"app.api.endpoints.torrent -> app.chain",
|
||||
"app.api.endpoints.torrent -> app.chain.media",
|
||||
@@ -2188,7 +2280,9 @@
|
||||
"app.api.endpoints.transfer -> app.adapters.web.security",
|
||||
"app.api.endpoints.transfer -> app.adapters.web.security.access",
|
||||
"app.api.endpoints.transfer -> app.api",
|
||||
"app.api.endpoints.transfer -> app.api.deps",
|
||||
"app.api.endpoints.transfer -> app.api.dependencies",
|
||||
"app.api.endpoints.transfer -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.transfer -> app.api.dependencies.history",
|
||||
"app.api.endpoints.transfer -> app.api.response",
|
||||
"app.api.endpoints.transfer -> app.application",
|
||||
"app.api.endpoints.transfer -> app.application.directory",
|
||||
@@ -2208,7 +2302,8 @@
|
||||
"app.api.endpoints.transfer -> app.schemas.types",
|
||||
"app.api.endpoints.transfer -> app.schemas.workflow",
|
||||
"app.api.endpoints.user -> app.api",
|
||||
"app.api.endpoints.user -> app.api.deps",
|
||||
"app.api.endpoints.user -> app.api.dependencies",
|
||||
"app.api.endpoints.user -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.user -> app.api.response",
|
||||
"app.api.endpoints.user -> app.application",
|
||||
"app.api.endpoints.user -> app.application.security",
|
||||
@@ -2233,7 +2328,9 @@
|
||||
"app.api.endpoints.workflow -> app.adapters.external",
|
||||
"app.api.endpoints.workflow -> app.adapters.external.server",
|
||||
"app.api.endpoints.workflow -> app.api",
|
||||
"app.api.endpoints.workflow -> app.api.deps",
|
||||
"app.api.endpoints.workflow -> app.api.dependencies",
|
||||
"app.api.endpoints.workflow -> app.api.dependencies.auth",
|
||||
"app.api.endpoints.workflow -> app.api.dependencies.workflow",
|
||||
"app.api.endpoints.workflow -> app.api.response",
|
||||
"app.api.endpoints.workflow -> app.application",
|
||||
"app.api.endpoints.workflow -> app.application.plugin",
|
||||
@@ -2289,7 +2386,8 @@
|
||||
"app.api.servarr -> app.adapters.web.security",
|
||||
"app.api.servarr -> app.adapters.web.security.access",
|
||||
"app.api.servarr -> app.api",
|
||||
"app.api.servarr -> app.api.deps",
|
||||
"app.api.servarr -> app.api.dependencies",
|
||||
"app.api.servarr -> app.api.dependencies.subscription",
|
||||
"app.api.servarr -> app.api.response",
|
||||
"app.api.servarr -> app.application",
|
||||
"app.api.servarr -> app.application.servarr",
|
||||
@@ -6122,7 +6220,7 @@
|
||||
"app.workflow.actions.transfer_file -> app.workflow",
|
||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||
],
|
||||
"module_count": 760,
|
||||
"module_count": 771,
|
||||
"modules": [
|
||||
"app",
|
||||
"app.adapters",
|
||||
@@ -6312,6 +6410,15 @@
|
||||
"app.api.apiv1",
|
||||
"app.api.context",
|
||||
"app.api.data",
|
||||
"app.api.dependencies",
|
||||
"app.api.dependencies.agent",
|
||||
"app.api.dependencies.auth",
|
||||
"app.api.dependencies.data",
|
||||
"app.api.dependencies.history",
|
||||
"app.api.dependencies.plugin",
|
||||
"app.api.dependencies.site",
|
||||
"app.api.dependencies.subscription",
|
||||
"app.api.dependencies.workflow",
|
||||
"app.api.deps",
|
||||
"app.api.endpoints",
|
||||
"app.api.endpoints.agent",
|
||||
@@ -6348,6 +6455,8 @@
|
||||
"app.api.endpoints.webhook",
|
||||
"app.api.endpoints.workflow",
|
||||
"app.api.openai_utils",
|
||||
"app.api.presentation",
|
||||
"app.api.presentation.sse",
|
||||
"app.api.principal",
|
||||
"app.api.response",
|
||||
"app.api.router_specs",
|
||||
|
||||
@@ -1317,7 +1317,7 @@
|
||||
"consumers": [],
|
||||
"producers": [
|
||||
{
|
||||
"caller": "app.api.deps",
|
||||
"caller": "app.api.dependencies.plugin",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
@@ -1514,7 +1514,7 @@
|
||||
],
|
||||
"producers": [
|
||||
{
|
||||
"caller": "app.api.deps",
|
||||
"caller": "app.api.dependencies.history",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
@@ -1598,7 +1598,7 @@
|
||||
],
|
||||
"producers": [
|
||||
{
|
||||
"caller": "app.api.deps",
|
||||
"caller": "app.api.dependencies.site",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
@@ -1625,7 +1625,7 @@
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"caller": "app.api.deps",
|
||||
"caller": "app.api.dependencies.site",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
@@ -1660,7 +1660,7 @@
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"caller": "app.api.deps",
|
||||
"caller": "app.api.dependencies.subscription",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user