# -*- coding: utf-8 -*- """댓글 라우터 (2-depth: 댓글+대댓글, 익명, 소프트 삭제).""" from collections import defaultdict from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from ..database import get_session from ..deps import Pagination, get_current_user, get_current_user_optional, get_pagination from ..models import Comment, Content, User from ..schemas import CommentItem, CommentReq, PaginatedResponse, ReplyItem router = APIRouter(prefix="/api/comment", tags=["Comment"]) @router.post("/content/{cid}") async def create_comment(cid: str, body: CommentReq, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session)): if not body.body.strip(): raise HTTPException(400, "내용을 입력해주세요.") content = await session.get(Content, cid) if content is None or content.is_deleted: raise HTTPException(404, "콘텐츠를 찾을 수 없습니다.") if body.parent_id is not None: parent = await session.get(Comment, body.parent_id) if parent is None or parent.content_id != cid: raise HTTPException(400, "원 댓글을 찾을 수 없습니다.") if parent.parent_id is not None: raise HTTPException(400, "대댓글에는 답글을 달 수 없습니다 (최대 2-depth).") c = Comment( content_id=cid, user_uuid=user.user_uuid, parent_id=body.parent_id, nickname=(body.nickname or user.nickname or "익명")[:50], body=body.body.strip()[:300], ) session.add(c) await session.commit() return {"id": c.id} @router.get("/content/{cid}", response_model=PaginatedResponse[CommentItem]) async def list_comments(cid: str, pg: Pagination = Depends(get_pagination), user: User | None = Depends(get_current_user_optional), session: AsyncSession = Depends(get_session)): me = user.user_uuid if user else None top_where = [Comment.content_id == cid, Comment.parent_id.is_(None)] total = (await session.execute(select(func.count(Comment.id)).where(*top_where))).scalar() or 0 tops = ( await session.execute( select(Comment).where(*top_where).order_by(Comment.created_at.desc()) .offset(pg.offset).limit(pg.page_size) ) ).scalars().all() parent_ids = [c.id for c in tops] replies_by_parent: dict[int, list[Comment]] = defaultdict(list) if parent_ids: reps = ( await session.execute( select(Comment).where(Comment.parent_id.in_(parent_ids)).order_by(Comment.created_at.asc()) ) ).scalars().all() for r in reps: replies_by_parent[r.parent_id].append(r) def out(c: Comment): return dict( id=c.id, body=(None if c.is_deleted else c.body), nickname=c.nickname, is_mine=(c.user_uuid == me), created_at=c.created_at, ) items = [ CommentItem(**out(c), replies=[ReplyItem(**out(r)) for r in replies_by_parent.get(c.id, [])]) for c in tops ] return PaginatedResponse.create(items, total, pg.page, pg.page_size) @router.delete("/{comment_id}") async def delete_comment(comment_id: int, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session)): c = await session.get(Comment, comment_id) if c is None or c.is_deleted: raise HTTPException(404, "댓글을 찾을 수 없습니다.") if c.user_uuid != user.user_uuid: raise HTTPException(403, "본인 댓글만 삭제할 수 있습니다.") c.is_deleted = True await session.commit() return {"ok": True}