ForcePilot/backend/server/routers/external_systems/trash_router.py
Kris ba798a9bb5 feat(routers): add external systems management module and its sub routers
新增外部系统限界上下文的完整路由体系,包含聚合路由层与23个子领域路由,覆盖外部系统全生命周期管理、健康检查、指标监控、审计日志、Token管理、通知渠道、回收站等功能,并将外部系统路由挂载到全局API前缀下。
2026-06-20 22:16:07 +08:00

138 lines
4.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.

"""Trash 子域 Router。
外部系统限界上下文的回收站管理 API覆盖回收站列表查询 / 恢复软删除资源 /
物理删除 / 清理过期回收站。所有端点通过 ``create_use_cases_from_db`` 装配
use_cases经 ``trash_service`` 端口调用用例。
回收站为高危操作,所有端点使用 ``get_admin_user`` 鉴权。Request Schema 与
Input DTO 不共享类Router 内显式构造 DTO操作者字段``restored_by`` /
``deleted_by`` / ``purged_by``)由 ``current_user.uid`` 填充。Router 内不
try/except 领域异常,由全局异常处理器统一处理。
路径顺序(关键,来自 FastAPI 路由匹配规则):静态路径端点(``GET ""`` /
``POST "/purge"``)必须在动态路径端点(``POST "/{resource_type}/{resource_id}/restore"``
/ ``DELETE "/{resource_type}/{resource_id}"``)之前声明。
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from fastapi import APIRouter, Depends, 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.trash import (
HardDeleteInput,
ListTrashInput,
PurgeTrashInput,
RestoreInput,
)
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_admin_user, get_db
trash_router = APIRouter(
prefix="/trash",
tags=["external-systems-trash"],
)
# ---------------- Request Schemas ----------------
class PurgeTrashRequest(BaseModel):
"""清理过期回收站请求体。字段对齐 ``PurgeTrashInput``。"""
model_config = ConfigDict(frozen=True)
older_than_days: int = Field(default=30, ge=1)
# ---------------- Endpoints ----------------
@trash_router.get("", response_model=dict)
async def list_trash(
resource_type: str | None = Query(None),
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
start_time: datetime | None = Query(None),
end_time: datetime | None = Query(None),
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 = ListTrashInput(
resource_type=resource_type,
limit=limit,
offset=offset,
start_time=start_time,
end_time=end_time,
)
output = await use_cases.trash_service.list_trash(input_dto)
return {"success": True, "data": output.model_dump()}
@trash_router.post("/purge", response_model=dict)
async def purge_trash(
payload: PurgeTrashRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""清理过期回收站(物理删除超过保留期的软删除资源)。
``purged_by`` 由当前管理员填充。
"""
use_cases = create_use_cases_from_db(db)
input_dto = PurgeTrashInput(
older_than_days=payload.older_than_days,
purged_by=current_user.uid,
)
output = await use_cases.trash_service.purge(input_dto)
return {"success": True, "data": output.model_dump()}
@trash_router.post("/{resource_type}/{resource_id}/restore", response_model=dict)
async def restore_resource(
resource_type: str,
resource_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""恢复软删除资源。
``restored_by`` 由当前管理员填充。
"""
use_cases = create_use_cases_from_db(db)
input_dto = RestoreInput(
resource_type=resource_type,
resource_id=resource_id,
restored_by=current_user.uid,
)
output = await use_cases.trash_service.restore(input_dto)
return {"success": True, "data": output.model_dump()}
@trash_router.delete("/{resource_type}/{resource_id}", response_model=dict)
async def hard_delete_resource(
resource_type: str,
resource_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""物理删除回收站中的资源(不可恢复)。
``deleted_by`` 由当前管理员填充。
"""
use_cases = create_use_cases_from_db(db)
input_dto = HardDeleteInput(
resource_type=resource_type,
resource_id=resource_id,
deleted_by=current_user.uid,
)
output = await use_cases.trash_service.hard_delete(input_dto)
return {"success": True, "data": output.model_dump()}