ForcePilot/backend/server/routers/channels/wizard_router.py
Kris ddafd95ff0 refactor(routers): 整理并新增多组渠道相关路由功能
1.  移除多个导出接口的显式response_model声明
2.  调整access_rule和test_case的创建接口位置,修复静态路径冲突
3.  优化适配器配置校验的异常处理逻辑
4.  重构集成路由的查询逻辑,统一使用get_integration_or_raise
5.  新增channels路由组下的capability、reports、dashboard、webhook、wizard、doctor、directory、session共8个子路由模块
6.  注册channels_router到全局路由列表
2026-07-02 03:29:06 +08:00

218 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""安装向导域 RouterWIZ-01~WIZ-05
本 router 实现渠道插件安装向导流程的 HTTP 端点,覆盖步骤查询 / 校验 /
应用 / 完成 / OAuth 回调共 5 个操作。所有端点统一采用模板 A控制面端口
路由),通过 ``get_channel_use_cases`` 装配 ``ChannelUseCases``,经
``wizard`` 端口调用类型化方法,由 ``ChannelControlService._executeControl``
内部走控制面管道auth → permission → rate_limit → dispatch → audit
鉴权策略:全部端点使用 ``get_admin_user`` 依赖,要求管理员或超级管理员角色。
角色校验由依赖函数完成router 内不做角色判断(规范 §4
路径设计:所有端点路径均为动态 ``/{channel_type}/wizard/*``,子 router
不自行设置 prefix根前缀 ``/channels`` 由 ``channels_router`` 聚合 router
统一追加。
端点清单对应《20-channels-wizard-domain-v1.0.md》§2.1
- GET /{channel_type}/wizard/steps WIZ-01 get_wizard_steps
- POST /{channel_type}/wizard/validate WIZ-02 validate_wizard_step
- POST /{channel_type}/wizard/steps/{step_id}/apply WIZ-03 apply_wizard_step
- POST /{channel_type}/wizard/finalize WIZ-04 finalize_wizard
- GET /{channel_type}/wizard/oauth-callback WIZ-05 handle_wizard_oauth_callback
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Query, Request
from pydantic import BaseModel, ConfigDict, Field
from yuxi.channels.contract.dtos.channel import ChannelType
from yuxi.storage.postgres.models_business import User
from server.routers.channels import (
build_operator,
get_channel_use_cases,
raiseOnControlFailure,
serialize_control_data,
)
from server.utils.auth_middleware import get_admin_user
wizard_router = APIRouter(tags=["channels-wizard"])
# ---------------- Request Schemas ----------------
class ValidateWizardRequest(BaseModel):
"""校验向导步骤请求。字段对齐 ``WizardPort.validateWizardStep`` 入参。"""
model_config = ConfigDict(frozen=True)
step_id: str = Field(..., min_length=1, description="向导步骤 ID")
values: dict[str, Any] = Field(..., description="步骤字段值字典")
class ApplyWizardRequest(BaseModel):
"""应用向导步骤请求。字段对齐 ``WizardPort.applyWizardStep`` 入参。"""
model_config = ConfigDict(frozen=True)
values: dict[str, Any] = Field(..., description="步骤字段值字典")
class WizardPatch(BaseModel):
"""向导配置补丁。字段对齐 ``WizardConfigPatch`` 值对象。
每项补丁含 ``step_id``(用于校验步骤存在性)与 ``values``(配置键值对),
在 router 层把好入口关,避免弱类型 ``dict[str, Any]`` 掩盖契约违反。
"""
model_config = ConfigDict(frozen=True)
step_id: str = Field(..., min_length=1, description="向导步骤 ID")
values: dict[str, Any] = Field(..., description="步骤字段值字典")
class FinalizeWizardRequest(BaseModel):
"""完成向导请求。字段对齐 ``WizardPort.finalizeWizard`` 入参。
``patches`` 至少含一项Port 前置条件要求非空),每项结构由
``WizardPatch`` 校验。转换为 ``list[dict[str, Any]]`` 后传入 Port
保持 Port 签名不变。
"""
model_config = ConfigDict(frozen=True)
patches: list[WizardPatch] = Field(..., min_length=1, description="配置补丁列表")
# ---------------- Endpoints ----------------
@wizard_router.get("/{channel_type}/wizard/steps", response_model=dict)
async def get_wizard_steps(
channel_type: ChannelType,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""查询安装向导步骤列表WIZ-01
对应控制面操作 ``wizard/steps``(由 ``ChannelControlService.getWizardSteps``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
步骤字段从已保存的向导状态预填充默认值,支持从现有配置恢复向导进度。
"""
operator = build_operator(current_user, request)
result = await use_cases.wizard.getWizardSteps(
channel_type=channel_type,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@wizard_router.post("/{channel_type}/wizard/validate", response_model=dict)
async def validate_wizard_step(
channel_type: ChannelType,
payload: ValidateWizardRequest,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""校验向导步骤字段值WIZ-02
对应控制面操作 ``wizard/validate``(由 ``ChannelControlService.validateWizardStep``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
仅校验字段值,不应用配置变更。校验通过时返回携带配置补丁的结果。
"""
operator = build_operator(current_user, request)
result = await use_cases.wizard.validateWizardStep(
channel_type=channel_type,
step_id=payload.step_id,
values=payload.values,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@wizard_router.post("/{channel_type}/wizard/steps/{step_id}/apply", response_model=dict)
async def apply_wizard_step(
channel_type: ChannelType,
step_id: str,
payload: ApplyWizardRequest,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""应用向导步骤WIZ-03
对应控制面操作 ``wizard/apply``(由 ``ChannelControlService.applyWizardStep``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
先校验步骤字段值,校验通过后应用步骤并将配置补丁合并到向导状态。
校验失败时不应用配置AC-39
"""
operator = build_operator(current_user, request)
result = await use_cases.wizard.applyWizardStep(
channel_type=channel_type,
step_id=step_id,
values=payload.values,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@wizard_router.post("/{channel_type}/wizard/finalize", response_model=dict)
async def finalize_wizard(
channel_type: ChannelType,
payload: FinalizeWizardRequest,
request: Request,
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""完成安装向导WIZ-04
对应控制面操作 ``wizard/finalize``(由 ``ChannelControlService.finalizeWizard``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
聚合请求中的配置补丁与向导状态中的已应用补丁,写入渠道配置并清除向导状态。
"""
operator = build_operator(current_user, request)
result = await use_cases.wizard.finalizeWizard(
channel_type=channel_type,
patches=[patch.model_dump() for patch in payload.patches],
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}
@wizard_router.get("/{channel_type}/wizard/oauth-callback", response_model=dict)
async def handle_wizard_oauth_callback(
channel_type: ChannelType,
request: Request,
code: str = Query(..., description="OAuth 授权码"),
state: str = Query(..., description="CSRF 防护参数"),
redirect_uri: str = Query(..., description="回调重定向 URI"),
use_cases=Depends(get_channel_use_cases),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""处理向导 OAuth 回调WIZ-05
对应控制面操作 ``wizard/oauth-callback``(由 ``ChannelControlService.handleWizardOAuthCallback``
内部构造 ``ControlCmd`` 并委托 ``_executeControl`` 执行控制面管道)。
委托向导适配器交换 OAuth 授权码并产出配置补丁,合并到向导状态。
"""
operator = build_operator(current_user, request)
result = await use_cases.wizard.handleWizardOAuthCallback(
channel_type=channel_type,
code=code,
state=state,
redirect_uri=redirect_uri,
operator=operator,
)
raiseOnControlFailure(result)
return {"success": True, "data": serialize_control_data(result.data)}