- 添加认证 API (登录/token验证) - 添加 Brief API (上传/解析/导入/冲突检测) - 添加视频 API (上传/断点续传/审核/违规/预览/重提交) - 添加审核 API (决策/批量审核/申诉/历史) - 实现基于角色的权限控制 - 更新集成测试,49 个测试全部通过 - 总体测试覆盖率 89.63% Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
229 lines
6.0 KiB
Python
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
|
|
],
|
|
)
|