Your Name f87ae48ad5 feat: 实现 FastAPI REST API 端点和集成测试
- 添加认证 API (登录/token验证)
- 添加 Brief API (上传/解析/导入/冲突检测)
- 添加视频 API (上传/断点续传/审核/违规/预览/重提交)
- 添加审核 API (决策/批量审核/申诉/历史)
- 实现基于角色的权限控制
- 更新集成测试,49 个测试全部通过
- 总体测试覆盖率 89.63%

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 18:08:12 +08:00

229 lines
6.0 KiB
Python

"""
Brief API 端点
"""
from fastapi import APIRouter, HTTPException, status, Header, UploadFile, File, Form
from pydantic import BaseModel, HttpUrl
from typing import Optional, Any
from datetime import datetime
import uuid
from app.api.v1.endpoints.auth import get_current_user
from app.services.brief_parser import (
BriefParser,
BriefFileValidator,
OnlineDocumentValidator,
OnlineDocumentImporter,
ParsingStatus,
)
from app.services.rule_engine import RuleConflictDetector
router = APIRouter()
# 模拟 Brief 存储
BRIEFS: dict[str, dict] = {
"brief_001": {
"brief_id": "brief_001",
"task_id": "task_001",
"platform": "douyin",
"status": "completed",
"selling_points": [
{"text": "24小时持妆", "priority": "high"},
{"text": "天然成分", "priority": "medium"},
],
"forbidden_words": [
{"word": "", "severity": "hard"},
{"word": "第一", "severity": "hard"},
],
"brand_tone": {"style": "年轻活力"},
"timing_requirements": [
{"type": "product_visible", "min_duration_seconds": 5},
{"type": "brand_mention", "min_frequency": 3},
],
"created_at": datetime.now().isoformat(),
},
}
class BriefUploadResponse(BaseModel):
parsing_id: str
status: str
message: str = ""
class BriefImportRequest(BaseModel):
url: str
task_id: str
class ConflictCheckRequest(BaseModel):
platform: str
class ConflictCheckResponse(BaseModel):
has_conflicts: bool
conflicts: list[dict[str, Any]]
@router.post("/upload", response_model=BriefUploadResponse, status_code=status.HTTP_202_ACCEPTED)
async def upload_brief(
file: UploadFile = File(...),
task_id: str = Form(...),
platform: str = Form("douyin"),
authorization: Optional[str] = Header(None),
):
"""上传 Brief 文件"""
# 验证认证
if not authorization:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authorization header required",
)
user = get_current_user(authorization)
# 验证文件格式
file_ext = file.filename.split(".")[-1].lower() if file.filename else ""
validator = BriefFileValidator()
if not validator.is_supported(file_ext):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Unsupported file format: {file_ext}",
)
# 创建解析任务
parsing_id = f"parsing_{uuid.uuid4().hex[:8]}"
# 模拟异步解析
brief_id = f"brief_{uuid.uuid4().hex[:8]}"
BRIEFS[brief_id] = {
"brief_id": brief_id,
"task_id": task_id,
"platform": platform,
"status": "processing",
"created_at": datetime.now().isoformat(),
}
return BriefUploadResponse(
parsing_id=parsing_id,
status="processing",
message="Brief is being processed",
)
@router.get("/{brief_id}")
async def get_brief(
brief_id: str,
authorization: Optional[str] = Header(None),
):
"""获取 Brief 解析结果"""
if not authorization:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authorization header required",
)
user = get_current_user(authorization)
brief = BRIEFS.get(brief_id)
if not brief:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Brief not found: {brief_id}",
)
return brief
@router.post("/import", response_model=BriefUploadResponse, status_code=status.HTTP_202_ACCEPTED)
async def import_online_document(
request: BriefImportRequest,
authorization: Optional[str] = Header(None),
):
"""导入在线文档"""
if not authorization:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authorization header required",
)
user = get_current_user(authorization)
# 验证 URL
validator = OnlineDocumentValidator()
if not validator.is_valid(request.url):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Unsupported document URL",
)
# 导入文档
importer = OnlineDocumentImporter()
result = importer.import_document(request.url)
if result.status == "failed":
if result.error_code == "ACCESS_DENIED":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=result.error_message,
)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.error_message,
)
parsing_id = f"parsing_{uuid.uuid4().hex[:8]}"
return BriefUploadResponse(
parsing_id=parsing_id,
status="processing",
)
@router.post("/{brief_id}/check_conflicts", response_model=ConflictCheckResponse)
async def check_rule_conflicts(
brief_id: str,
request: ConflictCheckRequest,
authorization: Optional[str] = Header(None),
):
"""检测规则冲突"""
if not authorization:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authorization header required",
)
user = get_current_user(authorization)
brief = BRIEFS.get(brief_id)
if not brief:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Brief not found: {brief_id}",
)
# 模拟平台规则
platform_rules = {
"platform": request.platform,
"forbidden_words": [
{"word": "", "category": "ad_law"},
{"word": "第一", "category": "ad_law"},
],
}
detector = RuleConflictDetector()
result = detector.detect_conflicts(brief, platform_rules)
return ConflictCheckResponse(
has_conflicts=result.has_conflicts,
conflicts=[
{
"type": c.conflict_type,
"description": c.description,
}
for c in result.conflicts
],
)