"""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, }, }