from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import get_current_active_user, get_db from app.models.comment import Comment from app.models.report import Report from app.models.spot import Spot from app.models.user import User from app.schemas.comment import CommentCreate, CommentOut, ReportCreate from app.schemas.common import PageResponse router = APIRouter() @router.post("/spots/{spot_id}/comments", response_model=CommentOut, status_code=status.HTTP_201_CREATED) async def create_comment( spot_id: int, payload: CommentCreate, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ): from app.services.content_safety import check_text safety = check_text(payload.content) if not safety["safe"]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"评论包含敏感词:{'、'.join(safety['matched'][:3])}", ) result = await db.execute(select(Spot).where(Spot.id == spot_id)) if not result.scalar_one_or_none(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Spot not found") if payload.parent_id is not None: parent_result = await db.execute( select(Comment).where(Comment.id == payload.parent_id, Comment.spot_id == spot_id) ) if not parent_result.scalar_one_or_none(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Parent comment not found") comment = Comment( spot_id=spot_id, user_id=current_user.id, parent_id=payload.parent_id, content=payload.content, ) db.add(comment) await db.commit() await db.refresh(comment) return comment @router.get("/spots/{spot_id}/comments", response_model=PageResponse[CommentOut]) async def list_comments( spot_id: int, page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), db: AsyncSession = Depends(get_db), ): base_filter = ( Comment.spot_id == spot_id, Comment.parent_id.is_(None), Comment.audit_status == "approved", ) count_result = await db.execute(select(func.count(Comment.id)).where(*base_filter)) total = count_result.scalar() or 0 offset = (page - 1) * page_size result = await db.execute( select(Comment) .where(*base_filter) .order_by(Comment.created_at.desc()) .offset(offset) .limit(page_size) ) top_comments = result.scalars().all() if top_comments: top_ids = [c.id for c in top_comments] replies_result = await db.execute( select(Comment) .where( Comment.parent_id.in_(top_ids), Comment.audit_status == "approved", ) .order_by(Comment.created_at.asc()) ) all_replies = replies_result.scalars().all() replies_map: dict[int, list] = {} for r in all_replies: replies_map.setdefault(r.parent_id, []).append(r) else: replies_map = {} items = [] for c in top_comments: out = CommentOut.model_validate(c) out.replies = [CommentOut.model_validate(r) for r in replies_map.get(c.id, [])] items.append(out) return PageResponse(total=total, items=items) @router.delete("/comments/{comment_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_comment( comment_id: int, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(Comment).where(Comment.id == comment_id)) comment = result.scalar_one_or_none() if not comment: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found") is_admin = current_user.role in ("admin", "moderator") if comment.user_id != current_user.id and not is_admin: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not allowed") await db.delete(comment) await db.commit() @router.post("/comments/{comment_id}/report", status_code=status.HTTP_201_CREATED) async def report_comment( comment_id: int, payload: ReportCreate, current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(Comment).where(Comment.id == comment_id)) if not result.scalar_one_or_none(): raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Comment not found") report = Report( reporter_id=current_user.id, target_type="comment", target_id=comment_id, reason=payload.reason, ) db.add(report) await db.commit() return {"code": 0, "message": "Report submitted"}