"""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()}