feat: add user role system and public joke submission API
- Add AdminUser role field (admin/submitter) - User management in admin panel - Public joke submission API (no auth required) - Document submission methods for external workers Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ceed63fcb0
commit
aecf1f1f15
@@ -5,10 +5,11 @@ from app.database import Base
|
||||
|
||||
|
||||
class AdminUser(Base):
|
||||
"""管理员用户表模型"""
|
||||
"""用户表 - 支持角色:admin(管理员), submitter(提交者)"""
|
||||
__tablename__ = "admin_users"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
username = Column(String(50), unique=True, nullable=False)
|
||||
password_hash = Column(String(255), nullable=False)
|
||||
role = Column(String(20), default="submitter") # admin=管理员, submitter=提交者
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
@@ -16,6 +16,8 @@ from app.schemas.category import JokeCrowdCreate, JokeCrowdResponse, JokeTypeCre
|
||||
from app.schemas.joke import JokeCreate, JokeResponse, JokeUpdate, PaginatedJokeResponse
|
||||
from app.schemas.link import LinkCreate, LinkResponse
|
||||
from app.schemas.feedback import FeedbackResponse
|
||||
from app.schemas.user import UserCreate, UserResponse
|
||||
from app.routers.auth import hash_password
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["管理后台"])
|
||||
|
||||
@@ -78,6 +80,8 @@ def joke_to_response(joke: Joke, db: Session = None) -> JokeResponse:
|
||||
title=joke.title,
|
||||
content=joke.content,
|
||||
polished_content=joke.polished_content,
|
||||
ai_score=joke.ai_score,
|
||||
ai_level=joke.ai_level,
|
||||
type_ids=ids,
|
||||
crowd_ids=crowd_ids,
|
||||
status=joke.status,
|
||||
@@ -96,15 +100,24 @@ def admin_list_jokes(
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
status: str = None,
|
||||
keyword: str = None,
|
||||
ai_level: str = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取所有笑话(支持状态筛选)"""
|
||||
"""获取所有笑话(支持状态筛选、关键词搜索)"""
|
||||
# 限制 page_size 防止 DoS
|
||||
page_size = max(1, min(page_size, 100))
|
||||
query = db.query(Joke)
|
||||
if status:
|
||||
query = query.filter(Joke.status == status)
|
||||
if keyword:
|
||||
keyword_filter = f"%{keyword}%"
|
||||
query = query.filter(
|
||||
(Joke.title.like(keyword_filter)) | (Joke.content.like(keyword_filter))
|
||||
)
|
||||
if ai_level:
|
||||
query = query.filter(Joke.ai_level == ai_level)
|
||||
total = query.count()
|
||||
offset = (page - 1) * page_size
|
||||
jokes = query.order_by(Joke.created_at.desc()).offset(offset).limit(page_size).all()
|
||||
@@ -159,6 +172,7 @@ def admin_update_joke(
|
||||
db_joke = db.query(Joke).filter(Joke.id == joke_id).first()
|
||||
if not db_joke:
|
||||
raise HTTPException(status_code=404, detail="笑话不存在")
|
||||
|
||||
update_data = joke.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
if key in ("type_ids", "crowd_ids") and value is not None:
|
||||
@@ -420,4 +434,68 @@ def admin_delete_feedback(
|
||||
raise HTTPException(status_code=404, detail="反馈不存在")
|
||||
db.delete(db_feedback)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 用户管理
|
||||
# ============================================================
|
||||
|
||||
@router.get("/users", response_model=list[UserResponse])
|
||||
def admin_list_users(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""获取所有用户"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="仅管理员可操作")
|
||||
return db.query(AdminUser).order_by(AdminUser.created_at.desc()).all()
|
||||
|
||||
|
||||
@router.post("/users", response_model=UserResponse)
|
||||
def admin_create_user(
|
||||
user: UserCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""创建用户"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="仅管理员可操作")
|
||||
|
||||
# 检查用户名是否已存在
|
||||
existing = db.query(AdminUser).filter(AdminUser.username == user.username).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
|
||||
db_user = AdminUser(
|
||||
username=user.username,
|
||||
password_hash=hash_password(user.password),
|
||||
role=user.role,
|
||||
)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
def admin_delete_user(
|
||||
user_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: AdminUser = Depends(get_current_admin_user),
|
||||
):
|
||||
"""删除用户"""
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(status_code=403, detail="仅管理员可操作")
|
||||
|
||||
db_user = db.query(AdminUser).filter(AdminUser.id == user_id).first()
|
||||
if not db_user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
|
||||
# 不允许删除自己
|
||||
if db_user.id == current_user.id:
|
||||
raise HTTPException(status_code=400, detail="不能删除自己")
|
||||
|
||||
db.delete(db_user)
|
||||
db.commit()
|
||||
return {"message": "删除成功"}
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
公开提交流程 - 供外部用户(submitter)使用。
|
||||
无后台管理权限,仅能提交笑话到 pending 状态。
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.database import get_db
|
||||
from app.models.joke import Joke
|
||||
|
||||
router = APIRouter(tags=["笑话提交"])
|
||||
|
||||
|
||||
class JokeSubmit(BaseModel):
|
||||
"""笑话提交"""
|
||||
title: str
|
||||
content: str
|
||||
|
||||
|
||||
class JokeBatchSubmit(BaseModel):
|
||||
"""批量提交"""
|
||||
jokes: list[JokeSubmit]
|
||||
|
||||
|
||||
@router.post("/submit", response_model=dict)
|
||||
def submit_joke(data: JokeSubmit, db: Session = Depends(get_db)):
|
||||
"""公开接口:提交单条笑话(自动进入待审核状态)"""
|
||||
joke = Joke(
|
||||
title=data.title,
|
||||
content=data.content,
|
||||
status="pending",
|
||||
)
|
||||
db.add(joke)
|
||||
db.commit()
|
||||
db.refresh(joke)
|
||||
return {
|
||||
"success": True,
|
||||
"id": joke.id,
|
||||
"message": f"已提交,ID: {joke.id}"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/submit/batch", response_model=dict)
|
||||
def submit_jokes_batch(data: JokeBatchSubmit, db: Session = Depends(get_db)):
|
||||
"""公开接口:批量提交笑话"""
|
||||
added = []
|
||||
for item in data.jokes:
|
||||
joke = Joke(
|
||||
title=item.title,
|
||||
content=item.content,
|
||||
status="pending",
|
||||
)
|
||||
db.add(joke)
|
||||
added.append(item.title)
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"success": True,
|
||||
"count": len(added),
|
||||
"message": f"已提交 {len(added)} 条笑话"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
"""创建用户"""
|
||||
username: str
|
||||
password: str
|
||||
role: str = "submitter" # admin=管理员, submitter=提交者
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
"""用户信息"""
|
||||
id: int
|
||||
username: str
|
||||
role: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserRegister(BaseModel):
|
||||
"""公开注册(仅限提交者)"""
|
||||
username: str
|
||||
password: str
|
||||
Reference in New Issue
Block a user