- 添加认证 API (登录/token验证) - 添加 Brief API (上传/解析/导入/冲突检测) - 添加视频 API (上传/断点续传/审核/违规/预览/重提交) - 添加审核 API (决策/批量审核/申诉/历史) - 实现基于角色的权限控制 - 更新集成测试,49 个测试全部通过 - 总体测试覆盖率 89.63% Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
145 lines
3.5 KiB
Python
145 lines
3.5 KiB
Python
"""
|
|
认证 API 端点
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, status
|
|
from pydantic import BaseModel, EmailStr
|
|
from typing import Optional
|
|
from datetime import datetime, timedelta
|
|
import secrets
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# 模拟用户数据库
|
|
MOCK_USERS = {
|
|
"agency@test.com": {
|
|
"user_id": "user_agency_001",
|
|
"email": "agency@test.com",
|
|
"password": "password",
|
|
"role": "agency",
|
|
"appeal_tokens": 5,
|
|
},
|
|
"creator@test.com": {
|
|
"user_id": "user_creator_001",
|
|
"email": "creator@test.com",
|
|
"password": "password",
|
|
"role": "creator",
|
|
"appeal_tokens": 3,
|
|
},
|
|
"reviewer@test.com": {
|
|
"user_id": "user_reviewer_001",
|
|
"email": "reviewer@test.com",
|
|
"password": "password",
|
|
"role": "reviewer",
|
|
"appeal_tokens": 0,
|
|
},
|
|
"brand@test.com": {
|
|
"user_id": "user_brand_001",
|
|
"email": "brand@test.com",
|
|
"password": "password",
|
|
"role": "brand",
|
|
"appeal_tokens": 0,
|
|
},
|
|
"no_token@test.com": {
|
|
"user_id": "user_no_token_001",
|
|
"email": "no_token@test.com",
|
|
"password": "password",
|
|
"role": "creator",
|
|
"appeal_tokens": 0,
|
|
},
|
|
}
|
|
|
|
# 模拟 token 存储
|
|
TOKENS: dict[str, dict] = {}
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
|
|
|
|
class LoginResponse(BaseModel):
|
|
access_token: str
|
|
token_type: str = "bearer"
|
|
user_id: str
|
|
role: str
|
|
expires_in: int = 3600
|
|
|
|
|
|
class UserProfile(BaseModel):
|
|
user_id: str
|
|
email: str
|
|
role: str
|
|
appeal_tokens: int
|
|
|
|
|
|
@router.post("/login", response_model=LoginResponse)
|
|
async def login(request: LoginRequest):
|
|
"""用户登录"""
|
|
user = MOCK_USERS.get(request.email)
|
|
|
|
if not user or user["password"] != request.password:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid email or password",
|
|
)
|
|
|
|
# 生成 token
|
|
token = secrets.token_urlsafe(32)
|
|
TOKENS[token] = {
|
|
"user_id": user["user_id"],
|
|
"email": user["email"],
|
|
"role": user["role"],
|
|
"expires_at": datetime.now() + timedelta(hours=1),
|
|
}
|
|
|
|
return LoginResponse(
|
|
access_token=token,
|
|
user_id=user["user_id"],
|
|
role=user["role"],
|
|
)
|
|
|
|
|
|
def get_current_user(token: str) -> dict:
|
|
"""验证 token 并返回用户信息"""
|
|
if not token or not token.startswith("Bearer "):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authorization header",
|
|
)
|
|
|
|
token_value = token[7:] # 移除 "Bearer " 前缀
|
|
token_data = TOKENS.get(token_value)
|
|
|
|
if not token_data:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid or expired token",
|
|
)
|
|
|
|
if datetime.now() > token_data["expires_at"]:
|
|
del TOKENS[token_value]
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Token expired",
|
|
)
|
|
|
|
return token_data
|
|
|
|
|
|
def get_user_by_id(user_id: str) -> dict | None:
|
|
"""根据 user_id 获取用户"""
|
|
for email, user in MOCK_USERS.items():
|
|
if user["user_id"] == user_id:
|
|
return user
|
|
return None
|
|
|
|
|
|
def update_user_tokens(user_id: str, delta: int) -> None:
|
|
"""更新用户申诉令牌"""
|
|
for email, user in MOCK_USERS.items():
|
|
if user["user_id"] == user_id:
|
|
user["appeal_tokens"] += delta
|
|
break
|