ForcePilot/backend/server/routers/external_systems/integration_router.py

286 lines
11 KiB
Python
Raw Normal View History

"""Integration 子域 Router.
外部系统限界上下文的厂商集成目录查询 API所有端点直接读取
``IntegrationRegistry`` ``IntegrationOperationRegistry`` 注册表全局单例
代码注册的内存态对象不引入 use_cases 数据源无持久化需求
adapter_router / auth_plugin_router 元数据端点保持一致
设计依据docs/vibe/v1.1/设计方案/RouterAPI扩展设计/15-integration_router扩展设计方案.md
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Query
from yuxi.external_systems.integrations.operation_registry import (
IntegrationOperationRegistry,
)
from yuxi.external_systems.integrations.registry import IntegrationRegistry
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_required_user
integration_router = APIRouter(
prefix="/integrations",
tags=["external-systems-integration"],
)
# 列表端点剔除的字段Schema/配置/依赖说明,避免列表响应过重
_SUMMARY_EXCLUDED_FIELDS = ("connection_extra_schema", "example_config", "dependency_note")
# =============================================================================
# === 静态路径端点(必须在 /{key} 之前声明) ===
# =============================================================================
@integration_router.get("", response_model=dict)
async def list_integrations(
adapter_type: str | None = Query(None, max_length=32, description="按适配器类型精确过滤"),
tags: str | None = Query(None, max_length=256, description="按标签过滤多个标签以逗号分隔OR 关系)"),
keyword: str | None = Query(
None, max_length=128, description="模糊匹配 display_name / description大小写不敏感"
),
limit: int = Query(50, ge=1, le=200, description="分页大小,最大 200"),
offset: int = Query(0, ge=0, description="分页偏移"),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询厂商集成目录列表,支持按适配器类型、标签、关键词过滤与分页。
列表项返回精简字段剔除 connection_extra_schema / example_config /
dependency_note适合前端集成市场页表格展示``total`` 为满足筛选
条件的全量总数 count_integrations 计算非当前分页条数
"""
tags_list: list[str] | None = None
if tags is not None:
tags_list = [t.strip() for t in tags.split(",") if t.strip()]
if not tags_list:
tags_list = None
metadata_list = IntegrationRegistry.list_integrations(
adapter_type=adapter_type,
tags=tags_list,
keyword=keyword,
limit=limit,
offset=offset,
)
total = IntegrationRegistry.count_integrations(
adapter_type=adapter_type,
tags=tags_list,
keyword=keyword,
)
items = [
{k: v for k, v in metadata.model_dump().items() if k not in _SUMMARY_EXCLUDED_FIELDS}
for metadata in metadata_list
]
return {
"success": True,
"data": {
"items": items,
"total": total,
"limit": limit,
"offset": offset,
},
}
@integration_router.get("/overview", response_model=dict)
async def get_integrations_overview(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询厂商集成的分组总览(按 adapter_type 分组 + 标签统计)。
通过 filter_integrations 获取全量集成无分页 ``adapter_type``
分组构造 ``by_adapter_type`` 字典每组仅含精简字段并统计每个标签
出现次数构造 ``by_tag`` 字典适合前端管理页树形渲染与统计卡片
"""
metadata_list = IntegrationRegistry.filter_integrations()
by_adapter_type: dict[str, list[dict[str, Any]]] = {}
by_tag: dict[str, int] = {}
for metadata in metadata_list:
by_adapter_type.setdefault(metadata.adapter_type, []).append(
{
"key": metadata.key,
"display_name": metadata.display_name,
"adapter_type": metadata.adapter_type,
"tags": metadata.tags,
}
)
for tag in metadata.tags:
by_tag[tag] = by_tag.get(tag, 0) + 1
return {
"success": True,
"data": {
"by_adapter_type": by_adapter_type,
"total": len(metadata_list),
"by_tag": by_tag,
},
}
@integration_router.get("/categories", response_model=dict)
async def get_integration_categories(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询厂商集成的分类标签树(基于 tags 字段聚合)。
通过 filter_integrations 获取全量集成无分页每个标签返回
``count`` ``integrations``key 列表 ``count`` 降序
``tag`` 升序排序适合前端集成市场页分类筛选侧边栏
"""
metadata_list = IntegrationRegistry.filter_integrations()
tag_to_integrations: dict[str, list[str]] = {}
for metadata in metadata_list:
for tag in metadata.tags:
tag_to_integrations.setdefault(tag, []).append(metadata.key)
items = [{"tag": tag, "count": len(keys), "integrations": keys} for tag, keys in tag_to_integrations.items()]
items.sort(key=lambda it: (-it["count"], it["tag"]))
return {
"success": True,
"data": {"items": items, "total": len(items)},
}
@integration_router.get("/adapter-types", response_model=dict)
async def get_integration_adapter_types(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询所有厂商集成涉及的适配器类型(去重),用于前端过滤选项。
仅返回有厂商集成的适配器类型当前全部为 http adapter_router
``GET /adapters``所有注册协议适配器语义不同
"""
adapter_types = IntegrationRegistry.list_adapter_types()
return {
"success": True,
"data": {"items": adapter_types, "total": len(adapter_types)},
}
@integration_router.get("/source-types", response_model=dict)
async def get_integration_source_types(
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询所有已注册操作 handler 的数据源类型(含操作支持矩阵)。
对每个 source_type 调用 ``list_operations`` 获取已注册操作
``source_type`` 升序排序适合前端"工具生成"页的 source_type 选择
仅返回厂商 source_type不含 openapi / postman 等通用 source_type
通用 source_type 走资产导入流程未注册操作 handler
"""
source_types = IntegrationOperationRegistry.list_source_types()
items = [
{
"source_type": source_type,
"operations": IntegrationOperationRegistry.list_operations(source_type),
}
for source_type in sorted(source_types)
]
return {
"success": True,
"data": {"items": items, "total": len(items)},
}
# =============================================================================
# === 路径参数端点(/{key} ===
# =============================================================================
@integration_router.get("/{key}", response_model=dict)
async def get_integration_detail(
key: str,
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询指定厂商集成的完整详情(含所有 12 个字段)。
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
由全局处理器统一映射返回 ``model_dump()`` 完整字段包含
connection_extra_schema / example_config / dependency_note
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
return {"success": True, "data": metadata.model_dump()}
@integration_router.get("/{key}/operations", response_model=dict)
async def get_integration_operations(
key: str,
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询指定厂商集成已注册的操作 handler 列表(按 source_type 分组)。
遍历 ``metadata.source_types``对每个 source_type 调用
``list_operations`` 获取已注册操作``operations_by_source_type``
``{source_type: [operations]}`` 字典``supported_operations`` 为所有
source_type 操作的并集去重按字母升序排序
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
operations_by_source_type: dict[str, list[str]] = {}
for source_type in metadata.source_types:
operations_by_source_type[source_type] = IntegrationOperationRegistry.list_operations(source_type)
supported_operations = sorted(set(op for ops in operations_by_source_type.values() for op in ops))
return {
"success": True,
"data": {
"key": key,
"operations_by_source_type": operations_by_source_type,
"supported_operations": supported_operations,
},
}
@integration_router.get("/{key}/connection-schema", response_model=dict)
async def get_integration_connection_schema(
key: str,
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""获取厂商集成的连接配置 JSON Schema用于前端动态表单渲染。
返回 ``connection_extra_schema`` 字段可能为 None表示该集成无额外
连接配置与详情端点的差异本端点仅返回 Schema适合前端表单渲染
时独立获取
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
return {
"success": True,
"data": {
"key": key,
"connection_extra_schema": metadata.connection_extra_schema,
},
}
@integration_router.get("/{key}/example-config", response_model=dict)
async def get_integration_example_config(
key: str,
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""获取厂商集成的推荐配置示例,用于前端表单预填与配置引导。
返回 ``example_config`` 字段可能为 None表示该集成无推荐配置
``get_integration_or_raise`` 未找到时抛 ``EntityNotFoundError`` (404)
"""
metadata = IntegrationRegistry.get_integration_or_raise(key)
return {
"success": True,
"data": {
"key": key,
"example_config": metadata.example_config,
},
}