"""安装向导域 Router(WIZ-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)}