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

175 lines
6.8 KiB
Python
Raw Normal View History

"""NotificationChannel 子域 Router。
外部系统限界上下文的通知渠道管理 API覆盖通知渠道列表 / 详情 / 创建 /
更新 / 删除 / 统计所有端点通过 ``create_use_cases_from_db`` 装配 use_cases
``notification_channel_service`` 端口调用用例
路径顺序约束静态路径``/stats``必须在 ``/{channel_id}`` 前声明
避免被路径参数捕获
认证策略查询类端点使用 ``get_required_user``写操作端点使用 ``get_admin_user``
Request Schema Input DTO 不共享类Router 内显式构造 DTO创建 / 更新
操作的 ``created_by`` / ``updated_by`` 字段由 ``current_user.uid`` 填充
"""
from __future__ import annotations
from typing import Any
from fastapi import APIRouter, Depends, Path, Query
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
from yuxi.external_systems.use_cases.dto.notification_channel import (
CreateNotificationChannelInput,
DeleteChannelInput,
GetChannelInput,
ListNotificationChannelsInput,
NotificationChannelStatsInput,
UpdateChannelInput,
)
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
notification_channel_router = APIRouter(
prefix="/notification-channels",
tags=["external-systems-notification-channel"],
)
# ---------------- Request Schemas ----------------
class CreateNotificationChannelRequest(BaseModel):
"""创建通知渠道请求体。字段对齐 ``CreateNotificationChannelInput``(不含 created_by
字段长度约束对齐 ``ExternalNotificationChannel`` ORM 列定义在边界层拦截非法输入
``channel_type`` 对齐 String(64)``name`` 对齐 String(128)``description`` 对齐 Text
"""
model_config = ConfigDict(frozen=True)
channel_type: str = Field(..., min_length=1, max_length=64)
name: str = Field(..., min_length=1, max_length=128)
config: dict[str, Any] = Field(default_factory=dict)
enabled: bool = True
description: str | None = Field(default=None, max_length=2000)
class UpdateNotificationChannelRequest(BaseModel):
"""更新通知渠道请求体。字段对齐 ``UpdateChannelInput``(不含 id / updated_by
字段长度约束对齐 ``ExternalNotificationChannel`` ORM 列定义仅透传客户端显式设置的字段
"""
model_config = ConfigDict(frozen=True)
name: str | None = Field(default=None, min_length=1, max_length=128)
config: dict[str, Any] | None = None
enabled: bool | None = None
description: str | None = Field(default=None, max_length=2000)
# ---------------- Endpoints ----------------
@notification_channel_router.get("", response_model=dict)
async def list_notification_channels(
limit: int = Query(100, ge=1, le=500),
offset: int = Query(0, ge=0),
channel_type: str | None = Query(None, max_length=32),
enabled: bool | None = Query(None),
keyword: str | None = Query(None, max_length=128),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""分页查询通知渠道列表(支持 channel_type / enabled / keyword 过滤)。"""
use_cases = create_use_cases_from_db(db)
input_dto = ListNotificationChannelsInput(
limit=limit,
offset=offset,
channel_type=channel_type,
enabled=enabled,
keyword=keyword,
)
output = await use_cases.notification_channel_service.list_channels(input_dto)
return {"success": True, "data": output.model_dump()}
@notification_channel_router.get("/stats", response_model=dict)
async def get_notification_channel_stats(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询通知渠道统计(按 channel_type 分组 + 按启用状态分组)。"""
use_cases = create_use_cases_from_db(db)
input_dto = NotificationChannelStatsInput()
output = await use_cases.notification_channel_service.get_channel_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@notification_channel_router.get("/{channel_id}", response_model=dict)
async def get_notification_channel(
channel_id: int = Path(ge=1),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""查询通知渠道详情。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetChannelInput(id=channel_id)
output = await use_cases.notification_channel_service.get_channel(input_dto)
return {"success": True, "data": output.model_dump()}
@notification_channel_router.post("", response_model=dict)
async def create_notification_channel(
payload: CreateNotificationChannelRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""创建通知渠道。``created_by`` 由当前管理员填充。"""
use_cases = create_use_cases_from_db(db)
input_dto = CreateNotificationChannelInput(
channel_type=payload.channel_type,
name=payload.name,
config=payload.config,
enabled=payload.enabled,
description=payload.description,
created_by=current_user.uid,
)
output = await use_cases.notification_channel_service.create_channel(input_dto)
return {"success": True, "data": output.model_dump()}
@notification_channel_router.put("/{channel_id}", response_model=dict)
async def update_notification_channel(
payload: UpdateNotificationChannelRequest,
channel_id: int = Path(ge=1),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""更新通知渠道。仅透传客户端显式设置的字段。``updated_by`` 由当前管理员填充。"""
use_cases = create_use_cases_from_db(db)
input_dto = UpdateChannelInput(
id=channel_id,
updated_by=current_user.uid,
**payload.model_dump(exclude_unset=True),
)
output = await use_cases.notification_channel_service.update_channel(input_dto)
return {"success": True, "data": output.model_dump()}
@notification_channel_router.delete("/{channel_id}", response_model=dict)
async def delete_notification_channel(
channel_id: int = Path(ge=1),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""删除通知渠道(软删除)。"""
use_cases = create_use_cases_from_db(db)
input_dto = DeleteChannelInput(id=channel_id)
output = await use_cases.notification_channel_service.delete_channel(input_dto)
return {"success": True, "data": output.model_dump()}