from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import or_ from sqlalchemy.orm import Session from typing import Optional from database import get_db from models.community import Post, Comment, PostFavorite from models.user import User from schemas.community import PostCreate, PostUpdate, PostOut, CommentCreate, CommentOut from services.auth import get_current_user, get_optional_current_user from routers.notification import push_notification router = APIRouter(prefix="/api/community", tags=["教师社区"]) def _sync_post_favorite_count(post: Post, db: Session) -> None: post.likes = db.query(PostFavorite).filter(PostFavorite.post_id == post.id).count() def _serialize_post(post: Post, current_user: User | None, db: Session) -> Post: is_favorited = False if current_user: is_favorited = db.query(PostFavorite).filter( PostFavorite.user_id == current_user.id, PostFavorite.post_id == post.id, ).first() is not None setattr(post, "is_favorited", is_favorited) setattr(post, "author", post.author) return post @router.post("/posts", response_model=PostOut, status_code=201) def create_post(data: PostCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): post = Post(user_id=current_user.id, **data.model_dump()) db.add(post) db.commit() db.refresh(post) return _serialize_post(post, current_user, db) @router.get("/posts", response_model=list[PostOut]) def list_posts( post_type: Optional[str] = None, keyword: Optional[str] = None, scope: Optional[str] = None, skip: int = Query(0, ge=0), limit: int = Query(20, ge=1, le=100), current_user: User | None = Depends(get_optional_current_user), db: Session = Depends(get_db), ): query = db.query(Post) if scope in {"mine", "favorite"} and not current_user: raise HTTPException(status_code=401, detail="请先登录") if scope == "mine": query = query.filter(Post.user_id == current_user.id) elif scope == "favorite": query = query.join(PostFavorite, PostFavorite.post_id == Post.id).filter( PostFavorite.user_id == current_user.id, ) if post_type: query = query.filter(Post.post_type == post_type) if keyword: query = query.filter(or_( Post.title.contains(keyword), Post.content.contains(keyword), )) posts = query.order_by(Post.is_pinned.desc(), Post.created_at.desc()).offset(skip).limit(limit).all() return [_serialize_post(post, current_user, db) for post in posts] @router.get("/posts/ranking", response_model=list[PostOut]) def ranking_posts( limit: int = Query(5, ge=1, le=20), current_user: User | None = Depends(get_optional_current_user), db: Session = Depends(get_db), ): score = Post.views + Post.likes * 10 + Post.comments_count * 3 posts = ( db.query(Post) .order_by(Post.is_pinned.desc(), score.desc(), Post.created_at.desc()) .limit(limit) .all() ) return [_serialize_post(post, current_user, db) for post in posts] @router.get("/posts/{post_id}", response_model=PostOut) def get_post( post_id: int, current_user: User | None = Depends(get_optional_current_user), db: Session = Depends(get_db), ): post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") post.views = (post.views or 0) + 1 db.commit() db.refresh(post) return _serialize_post(post, current_user, db) @router.put("/posts/{post_id}", response_model=PostOut) def update_post( post_id: int, data: PostUpdate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): post = db.query(Post).filter(Post.id == post_id, Post.user_id == current_user.id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") update_data = data.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(post, key, value) db.commit() db.refresh(post) return _serialize_post(post, current_user, db) @router.post("/posts/{post_id}/like", response_model=dict) def like_post( post_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") favorite = db.query(PostFavorite).filter( PostFavorite.user_id == current_user.id, PostFavorite.post_id == post_id, ).first() if not favorite: db.add(PostFavorite(user_id=current_user.id, post_id=post_id)) db.flush() push_notification(db, user_id=post.user_id, actor_id=current_user.id, ntype="like", title=f"{current_user.name} 收藏了你的帖子", content=post.title[:60], link=f"/community/{post_id}") _sync_post_favorite_count(post, db) db.commit() return {"success": True, "likes": post.likes, "is_favorited": True} @router.delete("/posts/{post_id}/like", response_model=dict) def unlike_post( post_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") favorite = db.query(PostFavorite).filter( PostFavorite.user_id == current_user.id, PostFavorite.post_id == post_id, ).first() if favorite: db.delete(favorite) db.flush() _sync_post_favorite_count(post, db) db.commit() return {"success": True, "likes": post.likes, "is_favorited": False} @router.get("/posts/{post_id}/comments", response_model=list[CommentOut]) def list_comments(post_id: int, skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), db: Session = Depends(get_db)): return db.query(Comment).filter(Comment.post_id == post_id).order_by(Comment.created_at).offset(skip).limit(limit).all() @router.post("/posts/{post_id}/comments", response_model=CommentOut, status_code=201) def create_comment( post_id: int, data: CommentCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") comment = Comment(post_id=post_id, user_id=current_user.id, **data.model_dump()) db.add(comment) post.comments_count = (post.comments_count or 0) + 1 push_notification(db, user_id=post.user_id, actor_id=current_user.id, ntype="comment", title=f"{current_user.name} 评论了你的帖子", content=post.title[:60], link=f"/community/{post_id}") db.commit() db.refresh(comment) return comment @router.delete("/posts/{post_id}/comments/{comment_id}", status_code=204) def delete_comment( post_id: int, comment_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db), ): post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") comment = db.query(Comment).filter(Comment.id == comment_id, Comment.post_id == post_id).first() if not comment: raise HTTPException(status_code=404, detail="评论不存在") if comment.user_id != current_user.id and post.user_id != current_user.id: raise HTTPException(status_code=403, detail="无权删除该评论") db.delete(comment) post.comments_count = max((post.comments_count or 0) - 1, 0) db.commit() @router.delete("/posts/{post_id}", status_code=204) def delete_post(post_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): post = db.query(Post).filter(Post.id == post_id, Post.user_id == current_user.id).first() if not post: raise HTTPException(status_code=404, detail="帖子不存在") db.delete(post) db.commit()