from fastapi import APIRouter, Depends, HTTPException, Header from jose import JWTError, jwt from sqlalchemy import func from sqlalchemy.orm import Session from app.config import JWT_ALGORITHM, JWT_SECRET_KEY from app.database import get_db from app.models.joke import Joke from app.models.category import JokeCrowd, JokeType from app.models.user import AdminUser from app.schemas.category import JokeCrowdCreate, JokeCrowdResponse, JokeTypeCreate, JokeTypeResponse from app.schemas.joke import JokeCreate, JokeResponse, JokeUpdate, PaginatedJokeResponse router = APIRouter(prefix="/admin", tags=["管理后台"]) def get_current_admin_user( authorization: str = Header(None), db: Session = Depends(get_db), ) -> AdminUser: """Dependency to get current authenticated admin user.""" if not authorization: raise HTTPException(status_code=401, detail="未提供认证令牌") parts = authorization.split() if len(parts) != 2 or parts[0].lower() != "bearer": raise HTTPException(status_code=401, detail="无效的认证格式") token = parts[1] try: payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM]) user_id = payload.get("sub") if user_id is None: raise HTTPException(status_code=401, detail="无效的令牌") except JWTError: raise HTTPException(status_code=401, detail="无效或过期的令牌") user = db.query(AdminUser).filter(AdminUser.id == int(user_id)).first() if not user: raise HTTPException(status_code=401, detail="用户不存在") return user def joke_to_response(joke: Joke) -> JokeResponse: """Convert Joke model to JokeResponse schema.""" return JokeResponse( id=joke.id, title=joke.title, content=joke.content, type_id=joke.type_id, crowd_id=joke.crowd_id, status=joke.status, view_count=joke.view_count, like_count=joke.like_count, created_at=joke.created_at, updated_at=joke.updated_at, type_name=joke.type.name if joke.type else None, crowd_name=joke.crowd.name if joke.crowd else None, ) # Admin Jokes Routes @router.get("/jokes", response_model=PaginatedJokeResponse) def admin_list_jokes( page: int = 1, page_size: int = 20, status: str = None, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """获取所有笑话(支持状态筛选)""" query = db.query(Joke) if status: query = query.filter(Joke.status == status) total = query.count() offset = (page - 1) * page_size jokes = query.order_by(Joke.created_at.desc()).offset(offset).limit(page_size).all() return PaginatedJokeResponse( items=[joke_to_response(j) for j in jokes], total=total, page=page, page_size=page_size, ) @router.post("/jokes", response_model=JokeResponse) def admin_create_joke( joke: JokeCreate, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """创建笑话""" db_joke = Joke(**joke.model_dump()) db.add(db_joke) db.commit() db.refresh(db_joke) return joke_to_response(db_joke) @router.put("/jokes/{joke_id}", response_model=JokeResponse) def admin_update_joke( joke_id: int, joke: JokeUpdate, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """更新笑话""" 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(): setattr(db_joke, key, value) db.commit() db.refresh(db_joke) return joke_to_response(db_joke) @router.delete("/jokes/{joke_id}") def admin_delete_joke( joke_id: int, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """删除笑话""" db_joke = db.query(Joke).filter(Joke.id == joke_id).first() if not db_joke: raise HTTPException(status_code=404, detail="笑话不存在") db.delete(db_joke) db.commit() return {"message": "删除成功"} @router.put("/jokes/batch-approve") def admin_batch_approve( joke_ids: list[int], db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """批量审核通过笑话""" jokes = db.query(Joke).filter(Joke.id.in_(joke_ids)).all() for joke in jokes: joke.status = "approved" db.commit() return {"message": f"已审核通过 {len(jokes)} 条笑话"} @router.get("/stats") def admin_stats( db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """获取统计数据""" total_jokes = db.query(Joke).count() approved_jokes = db.query(Joke).filter(Joke.status == "approved").count() pending_jokes = db.query(Joke).filter(Joke.status == "pending").count() total_views = db.query(Joke).with_entities(func.sum(Joke.view_count)).scalar() or 0 total_likes = db.query(Joke).with_entities(func.sum(Joke.like_count)).scalar() or 0 return { "total_jokes": total_jokes, "approved_jokes": approved_jokes, "pending_jokes": pending_jokes, "total_views": total_views, "total_likes": total_likes, } # Categories CRUD @router.post("/types", response_model=JokeTypeResponse) def admin_create_type( joke_type: JokeTypeCreate, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """创建笑话类型""" db_type = JokeType(name=joke_type.name, icon=joke_type.icon, sort_order=joke_type.sort_order) db.add(db_type) db.commit() db.refresh(db_type) return JokeTypeResponse.model_validate(db_type) @router.put("/types/{type_id}", response_model=JokeTypeResponse) def admin_update_type( type_id: int, joke_type: JokeTypeResponse, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """更新笑话类型""" db_type = db.query(JokeType).filter(JokeType.id == type_id).first() if not db_type: raise HTTPException(status_code=404, detail="类型不存在") db_type.name = joke_type.name if joke_type.icon is not None: db_type.icon = joke_type.icon if joke_type.sort_order is not None: db_type.sort_order = joke_type.sort_order db.commit() db.refresh(db_type) return JokeTypeResponse.model_validate(db_type) @router.delete("/types/{type_id}") def admin_delete_type( type_id: int, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """删除笑话类型""" db_type = db.query(JokeType).filter(JokeType.id == type_id).first() if not db_type: raise HTTPException(status_code=404, detail="类型不存在") # Check if there are jokes using this type joke_count = db.query(Joke).filter(Joke.type_id == type_id).count() if joke_count > 0: raise HTTPException(status_code=400, detail=f"有 {joke_count} 条笑话使用此类型,无法删除") db.delete(db_type) db.commit() return {"message": "删除成功"} @router.post("/crowds", response_model=JokeCrowdResponse) def admin_create_crowd( crowd: JokeCrowdCreate, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """创建笑话人群分类""" db_crowd = JokeCrowd(name=crowd.name, icon=crowd.icon, sort_order=crowd.sort_order) db.add(db_crowd) db.commit() db.refresh(db_crowd) return JokeCrowdResponse.model_validate(db_crowd) @router.put("/crowds/{crowd_id}", response_model=JokeCrowdResponse) def admin_update_crowd( crowd_id: int, crowd: JokeCrowdResponse, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """更新笑话人群分类""" db_crowd = db.query(JokeCrowd).filter(JokeCrowd.id == crowd_id).first() if not db_crowd: raise HTTPException(status_code=404, detail="人群分类不存在") db_crowd.name = crowd.name if crowd.icon is not None: db_crowd.icon = crowd.icon if crowd.sort_order is not None: db_crowd.sort_order = crowd.sort_order db.commit() db.refresh(db_crowd) return JokeCrowdResponse.model_validate(db_crowd) @router.delete("/crowds/{crowd_id}") def admin_delete_crowd( crowd_id: int, db: Session = Depends(get_db), current_user: AdminUser = Depends(get_current_admin_user), ): """删除笑话人群分类""" db_crowd = db.query(JokeCrowd).filter(JokeCrowd.id == crowd_id).first() if not db_crowd: raise HTTPException(status_code=404, detail="人群分类不存在") # Check if there are jokes using this crowd joke_count = db.query(Joke).filter(Joke.crowd_id == crowd_id).count() if joke_count > 0: raise HTTPException(status_code=400, detail=f"有 {joke_count} 条笑话使用此人群,无法删除") db.delete(db_crowd) db.commit() return {"message": "删除成功"}