from fastapi import APIRouter, Query, Depends, Path
from fastapi.responses import JSONResponse
from datetime import datetime, timezone
import logging
from backend.schemas import ShareCreate, ShareUpdate, CommentCreate
from backend.jwt_handler import TokenData, get_current_user_token_data
from backend import database
router = APIRouter(prefix="/api/shares", tags=["分享评论"]) # 前缀统一为/api/shares
logger = logging.getLogger(__name__)
@router.post("") # 原路径/api/shares
async def create_share(
share_data: ShareCreate,
current_user: TokenData = Depends(get_current_user_token_data)
):
"""发布AI聊天分享"""
# 校验关联AI角色(可选)
if share_data.ai_character_id:
char_exist = await database.get_character_by_id(share_data.ai_character_id)
if not char_exist:
return JSONResponse(
{"success": False, "message": "关联的AI角色不存在"},
status_code=404
)
# 执行发布
new_share = await database.create_share(
title=share_data.title,
content=share_data.content,
ai_character_id=share_data.ai_character_id,
is_public=share_data.is_public,
author_id=current_user.user_id,
created_at=datetime.now(timezone.utc)
)
logger.info(f"📤 User {current_user.user_id} created share {new_share['id']}")
return JSONResponse(
{"success": True, "data": new_share},
status_code=201
)
@router.get("") # 原路径/api/shares
async def get_share_list(
sort: str = Query("new", pattern="^(new|hot)$"),
page: int = Query(1, ge=1),
size: int = Query(10, ge=1),
current_user: TokenData = Depends(get_current_user_token_data)
):
"""获取分享列表(按最新/最热排序,公开+本人私有)"""
# 排序规则:new=时间倒序,hot=点赞数倒序
order_by = "created_at DESC" if sort == "new" else "like_count DESC"
# 筛选规则:公开分享所有人可见,私有仅作者可见
author_filter = current_user.user_id if current_user else None
total, shares = await database.get_shares(
is_public=True, author_id=author_filter, order_by=order_by, page=page, size=size
)
return JSONResponse(
{"success": True, "data": {
"total": total,
"page": page,
"size": size,
"items": shares
}}
)
@router.get("/{share_id}") # 原路径/api/shares/{share_id}
async def get_share_detail(
share_id: int = Path(..., ge=1),
current_user: TokenData = Depends(get_current_user_token_data)
):
"""获取分享详情(私有仅作者/管理员可见)"""
share = await database.get_share_by_id(share_id)
if not share:
return JSONResponse(
{"success": False, "message": "分享不存在"},
status_code=404
)
# 权限校验:私有分享仅作者或管理员可见
if not share["is_public"]:
if share["author_id"] != current_user.user_id and current_user.role != "admin":
return JSONResponse(
{"success": False, "message": "无权限查看私有分享"},
status_code=403
)
# 浏览量+1
await database.update_share(share_id, {"view_count": share["view_count"] + 1})
return JSONResponse({"success": True, "data": share})
@router.put("/{share_id}") # 原路径/api/shares/{share_id}
async def update_share(
share_id: int = Path(..., ge=1),
update_data: ShareUpdate = Depends(),
current_user: TokenData = Depends(get_current_user_token_data)
):
"""更新分享(仅作者可操作)"""
share = await database.get_share_by_id(share_id)
if not share:
return JSONResponse(
{"success": False, "message": "分享不存在"},
status_code=404
)
# 权限校验
if share["author_id"] != current_user.user_id:
return JSONResponse(
{"success": False, "message": "仅作者可修改分享"},
status_code=403
)
# 执行更新(仅更新传入的字段)
await database.update_share(share_id, update_data.dict(exclude_unset=True))
logger.info(f"🔄 User {current_user.user_id} updated share {share_id}")
return JSONResponse({"success": True, "message": "分享已更新"})
@router.delete("/{share_id}") # 原路径/api/shares/{share_id}
async def delete_share(
share_id: int = Path(..., ge=1),
current_user: TokenData = Depends(get_current_user_token_data)
):
"""删除分享(作者/管理员可操作)"""
share = await database.get_share_by_id(share_id)
if not share:
return JSONResponse(
{"success": False, "message": "分享不存在"},
status_code=404
)
# 权限校验
if share["author_id"] != current_user.user_id and current_user.role != "admin":
return JSONResponse(
{"success": False, "message": "仅作者或管理员可删除"},
status_code=403
)
# 执行删除
await database.delete_share(share_id)
logger.info(f"🗑️ User {current_user.user_id} deleted share {share_id}")
return JSONResponse({"success": True, "message": "分享已删除"})
@router.post("/{share_id}/comments") # 原路径/api/shares/{share_id}/comments
async def create_comment(
share_id: int = Path(..., ge=1),
comment_data: CommentCreate = Depends(),
current_user: TokenData = Depends(get_current_user_token_data)
):
"""发表评论(支持回复父评论)"""
# 校验分享存在且可评论
share = await database.get_share_by_id(share_id)
if not share:
return JSONResponse(
{"success": False, "message": "分享不存在"},
status_code=404
)
if not share["is_public"] and share["author_id"] != current_user.user_id:
return JSONResponse(
{"success": False, "message": "私有分享仅作者可评论"},
status_code=403
)
# 校验父评论存在(如有)
if comment_data.parent_id:
parent_comment = await database.get_comment_by_id(comment_data.parent_id)
if not parent_comment or parent_comment["share_id"] != share_id:
return JSONResponse(
{"success": False, "message": "父评论不存在或不属于当前分享"},
status_code=404
)
# 执行创建
new_comment = await database.create_comment(
share_id=share_id,
commenter_id=current_user.user_id,
content=comment_data.content,
parent_id=comment_data.parent_id,
created_at=datetime.now(timezone.utc)
)
# 分享评论数+1
await database.update_share(share_id, {"comment_count": share["comment_count"] + 1})
logger.info(f"💬 User {current_user.user_id} commented on share {share_id}")
return JSONResponse(
{"success": True, "data": new_comment},
status_code=201
)
@router.get("/{share_id}/comments") # 原路径/api/shares/{share_id}/comments
async def get_share_comments(
share_id: int = Path(..., ge=1),
page: int = Query(1, ge=1),
size: int = Query(20, ge=1),
current_user: TokenData = Depends(get_current_user_token_data)
):
"""获取分享评论(分页,含子评论)"""
# 校验分享存在且可查看
share = await database.get_share_by_id(share_id)
if not share:
return JSONResponse(
{"success": False, "message": "分享不存在"},
status_code=404
)
if not share["is_public"] and share["author_id"] != current_user.user_id:
return JSONResponse(
{"success": False, "message": "无权限查看评论"},
status_code=403
)
# 获取评论
total, comments = await database.get_share_comments(
share_id=share_id, page=page, size=size, order_by="created_at DESC"
)
return JSONResponse(
{"success": True, "data": {
"total": total,
"page": page,
"size": size,
"items": comments
}}
)
@router.post("/{share_id}/like") # 原路径/api/shares/{share_id}/like
async def like_share(
share_id: int = Path(..., ge=1),
like: bool = Query(...), # True=点赞,False=取消
current_user: TokenData = Depends(get_current_user_token_data)
):
"""点赞/取消点赞分享"""
share = await database.get_share_by_id(share_id)
if not share:
return JSONResponse(
{"success": False, "message": "分享不存在"},
status_code=404
)
# 校验状态
has_liked = await database.check_share_like(share_id, current_user.user_id)
if like and has_liked:
return JSONResponse(
{"success": False, "message": "已点赞该分享"},
status_code=400
)
if not like and not has_liked:
return JSONResponse(
{"success": False, "message": "未点赞该分享,无法取消"},
status_code=400
)
# 执行点赞/取消
if like:
await database.add_share_like(share_id, current_user.user_id)
new_like_count = share["like_count"] + 1
msg = "点赞成功"
else:
await database.remove_share_like(share_id, current_user.user_id)
new_like_count = max(0, share["like_count"] - 1)
msg = "取消点赞成功"
# 更新点赞数
await database.update_share(share_id, {"like_count": new_like_count})
logger.info(f"❤️ User {current_user.user_id} {'liked' if like else 'unliked'} share {share_id}")
return JSONResponse(
{"success": True, "message": msg, "data": {"like_count": new_like_count}}
)
from fastapi import APIRouter, Request, Depends, Query
from fastapi.responses import JSONResponse
from datetime import timedelta
import logging
from backend.schemas import UserCreate, UserUpdate
from backend.jwt_handler import (
ACCESS_TOKEN_EXPIRE_MINUTES,
TokenData,
get_admin_user,
get_current_user_token_data,
verify_password,
get_password_hash,
create_access_token
)
from backend import database
router = APIRouter(prefix="/api/users", tags=["用户管理"]) # 前缀统一为/api/users,便于区分
logger = logging.getLogger(__name__)
@router.post("/login", tags=["用户认证"]) # 登录接口路径不变(原路径为/login,不加前缀)
async def login(request: Request):
"""用户登录"""
data = await request.json()
account = data.get("account")
password = data.get("password")
# 基础校验
if not account or not password:
logger.warning(f"Login failed: missing credentials from {request.client.host}")
return JSONResponse(
{"success": False, "message": "请输入用户名和密码"},
status_code=400
)
# 校验用户存在性
hash_password = get_password_hash(password)
result = await database.check_users(account, hash_password)
if not result:
logger.warning(f"Login failed: user {account} not found")
return JSONResponse(
{"success": False, "message": "用户名或密码错误"},
status_code=401
)
user_id, hashed_pwd_from_db = result
# 校验密码正确性
if not verify_password(password, hashed_pwd_from_db):
logger.warning(f"Login failed: wrong pwd for {account}")
return JSONResponse(
{"success": False, "message": "用户名或密码错误"},
status_code=401
)
# 生成含权限的Token
user_from_db = await database.get_user_by_account(account)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={
"sub": str(user_from_db["id"]),
"role": user_from_db["role"],
"dept_id": user_from_db["department_id"]
},
expires_delta=access_token_expires
)
logger.info(f"🔐 User {user_id} logged in successfully")
# 设置HttpOnly Cookie
response = JSONResponse({"success": True, "account": account})
response.set_cookie(
key="access_token",
value=access_token,
httponly=True,
secure=False,
samesite="lax",
max_age=access_token_expires.total_seconds()
)
return response
@router.post("/register", tags=["用户认证"]) # 原路径/api/register
async def user_register(user_data: UserCreate):
"""用户注册"""
# 校验账号唯一性
existing_user = await database.get_user_by_account(user_data.account)
if existing_user:
logger.warning(f"Register failed: account {user_data.account} exists")
return JSONResponse(
{"success": False, "message": "用户名已存在"},
status_code=400
)
# 密码哈希+创建用户
hashed_pwd = get_password_hash(user_data.password)
new_user = await database.create_user(
account=user_data.account,
password=hashed_pwd,
role="user",
department_id=user_data.department_id
)
logger.info(f"🆕 User {new_user['id']} registered successfully")
return JSONResponse(
{"success": True, "data": {"user_id": new_user["id"], "account": new_user["account"]}},
status_code=201
)
@router.get("/me", tags=["用户信息"]) # 原路径/api/users/me
async def get_current_user_info(
current_user: TokenData = Depends(get_current_user_token_data)
):
"""获取当前用户详情"""
user_detail = await database.get_user_detail(current_user.user_id)
return JSONResponse(
{"success": True, "data": {
"user_id": user_detail["id"],
"account": user_detail["account"],
"role": user_detail["role"],
"department": {
"id": user_detail["department_id"],
"name": user_detail["dept_name"]
},
"created_at": user_detail["created_at"].strftime("%Y-%m-%d %H:%M:%S")
}}
)
@router.put("/me", tags=["用户信息"]) # 原路径/api/users/me
async def update_current_user(
update_data: UserUpdate,
current_user: TokenData = Depends(get_current_user_token_data)
):
"""更新当前用户信息"""
update_params = {}
# 密码更新:重新哈希
if update_data.password:
update_params["password"] = get_password_hash(update_data.password)
# 院系更新:校验院系存在
if update_data.department_id:
dept_exist = await database.get_department_by_id(update_data.department_id)
if not dept_exist:
return JSONResponse(
{"success": False, "message": "目标院系不存在"},
status_code=404
)
update_params["department_id"] = update_data.department_id
# 执行更新
await database.update_user(current_user.user_id, update_params)
logger.info(f"🔄 User {current_user.user_id} updated info: {list(update_params.keys())}")
return JSONResponse({"success": True, "message": "个人信息更新成功"})
@router.get("/admin/list", tags=["管理员-用户管理"]) # 原路径/api/admin/users
async def get_admin_user_list(
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=50),
role: str = Query(None, pattern="^(admin|user|dept_admin)$"),
dept_id: int = Query(None),
admin: TokenData = Depends(get_admin_user)
):
"""管理员查询用户列表"""
total, users = await database.get_users_list(
page=page, size=size, role=role, dept_id=dept_id
)
return JSONResponse(
{"success": True, "data": {
"total": total,
"page": page,
"size": size,
"items": [
{
"user_id": u["id"],
"account": u["account"],
"role": u["role"],
"department_id": u["department_id"],
"created_at": u["created_at"].strftime("%Y-%m-%d %H:%M:%S")
} for u in users
]
}}
)
@router.put("/admin/{user_id}/role", tags=["管理员-用户管理"]) # 原路径/api/admin/users/{user_id}/role
async def update_user_role(
user_id: str,
new_role: str = Query(..., pattern="^(admin|user|dept_admin)$"),
admin: TokenData = Depends(get_admin_user)
):
"""管理员修改用户角色"""
# 校验用户存在
user_exist = await database.get_user_by_id(user_id)
if not user_exist:
return JSONResponse(
{"success": False, "message": "目标用户不存在"},
status_code=404
)
# 执行角色更新
await database.update_user(user_id, {"role": new_role})
logger.info(f"🔧 Admin {admin.user_id} updated user {user_id} role to {new_role}")
return JSONResponse({"success": True, "message": f"用户角色已更新为{new_role}"})
对这些接口也做数据库集成